org.apdplat.platform.spring.APDPlatPersistenceUnitReader.java Source code

Java tutorial

Introduction

Here is the source code for org.apdplat.platform.spring.APDPlatPersistenceUnitReader.java

Source

/**
 * 
 * APDPlat - Application Product Development Platform
 * Copyright (c) 2013, ??, yang-shangchuan@qq.com
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package org.apdplat.platform.spring;

import org.apdplat.module.system.service.PropertyHolder;
import org.apdplat.platform.log.APDPlatLogger;
import org.apdplat.platform.util.FileUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.util.ResourceUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.util.xml.SimpleSaxErrorHandler;

import java.lang.instrument.ClassFileTransformer;
import java.net.URL;
import java.security.ProtectionDomain;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.persistence.spi.ClassTransformer;
import org.apdplat.platform.log.APDPlatLoggerFactory;

import org.springframework.core.DecoratingClassLoader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.instrument.classloading.SimpleThrowawayClassLoader;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * Internal helper class for reading <code>persistence.xml</code> files.
 *
 * @author Costin Leau
 * @author Juergen Hoeller
 * @since 2.0
 */
class APDPlatPersistenceUnitReader {

    private static final String PERSISTENCE_VERSION = "version";

    private static final String PERSISTENCE_UNIT = "persistence-unit";

    private static final String UNIT_NAME = "name";

    private static final String MAPPING_FILE_NAME = "mapping-file";

    private static final String JAR_FILE_URL = "jar-file";

    private static final String MANAGED_CLASS_NAME = "class";

    private static final String PROPERTIES = "properties";

    private static final String PROVIDER = "provider";

    private static final String TRANSACTION_TYPE = "transaction-type";

    private static final String JTA_DATA_SOURCE = "jta-data-source";

    private static final String NON_JTA_DATA_SOURCE = "non-jta-data-source";

    private static final String EXCLUDE_UNLISTED_CLASSES = "exclude-unlisted-classes";

    private static final String SHARED_CACHE_MODE = "shared-cache-mode";

    private static final String VALIDATION_MODE = "validation-mode";

    private static final String META_INF = "META-INF";

    protected final APDPlatLogger logger = APDPlatLoggerFactory.getAPDPlatLogger(getClass());

    private final ResourcePatternResolver resourcePatternResolver;

    private final DataSourceLookup dataSourceLookup;

    /**
     * Create a new APDPlatPersistenceUnitReader.
     * @param resourcePatternResolver the ResourcePatternResolver to use for loading resources
     * @param dataSourceLookup the DataSourceLookup to resolve DataSource names in
     * <code>persistence.xml</code> files against
     */
    public APDPlatPersistenceUnitReader(ResourcePatternResolver resourcePatternResolver,
            DataSourceLookup dataSourceLookup) {
        Assert.notNull(resourcePatternResolver, "ResourceLoader must not be null");
        Assert.notNull(dataSourceLookup, "DataSourceLookup must not be null");
        this.resourcePatternResolver = resourcePatternResolver;
        this.dataSourceLookup = dataSourceLookup;
    }

    /**
     * Parse and build all persistence unit infos defined in the specified XML file(s).
     * @param persistenceXmlLocation the resource location (can be a pattern)
     * @return the resulting PersistenceUnitInfo instances
     */
    public SpringPersistenceUnitInfo[] readPersistenceUnitInfos(String persistenceXmlLocation) {
        return readPersistenceUnitInfos(new String[] { persistenceXmlLocation });
    }

    /**
     * Parse and build all persistence unit infos defined in the given XML files.
     * @param persistenceXmlLocations the resource locations (can be patterns)
     * @return the resulting PersistenceUnitInfo instances
     */
    public SpringPersistenceUnitInfo[] readPersistenceUnitInfos(String[] persistenceXmlLocations) {
        ErrorHandler handler = new SimpleSaxErrorHandler(LogFactory.getLog(getClass()));
        List<SpringPersistenceUnitInfo> infos = new LinkedList<SpringPersistenceUnitInfo>();
        String resourceLocation = null;
        try {
            for (String location : persistenceXmlLocations) {
                Resource[] resources = this.resourcePatternResolver.getResources(location);
                for (Resource resource : resources) {
                    resourceLocation = resource.toString();
                    InputStream stream = resource.getInputStream();
                    try {
                        Document document = buildDocument(handler, stream);
                        parseDocument(resource, document, infos);
                    } finally {
                        stream.close();
                    }
                }
            }
        } catch (IOException ex) {
            throw new IllegalArgumentException("Cannot parse persistence unit from " + resourceLocation, ex);
        } catch (SAXException ex) {
            throw new IllegalArgumentException("Invalid XML in persistence unit from " + resourceLocation, ex);
        } catch (ParserConfigurationException ex) {
            throw new IllegalArgumentException("Internal error parsing persistence unit from " + resourceLocation);
        }

        return infos.toArray(new SpringPersistenceUnitInfo[infos.size()]);
    }

    /**
     * Validate the given stream and return a valid DOM document for parsing.
     */
    protected Document buildDocument(ErrorHandler handler, InputStream stream)
            throws ParserConfigurationException, SAXException, IOException {

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder parser = dbf.newDocumentBuilder();
        parser.setErrorHandler(handler);
        return parser.parse(stream);
    }

    /**
     * Parse the validated document and add entries to the given unit info list.
     */
    protected List<SpringPersistenceUnitInfo> parseDocument(Resource resource, Document document,
            List<SpringPersistenceUnitInfo> infos) throws IOException {

        Element persistence = document.getDocumentElement();
        String version = persistence.getAttribute(PERSISTENCE_VERSION);
        URL unitRootURL = determinePersistenceUnitRootUrl(resource);
        List<Element> units = DomUtils.getChildElementsByTagName(persistence, PERSISTENCE_UNIT);
        for (Element unit : units) {
            SpringPersistenceUnitInfo info = parsePersistenceUnitInfo(unit, version);
            info.setPersistenceUnitRootUrl(unitRootURL);
            infos.add(info);
        }

        return infos;
    }

    /**
     * Determine the persistence unit root URL based on the given resource
     * (which points to the <code>persistence.xml</code> file we're reading).
     * @param resource the resource to check
     * @return the corresponding persistence unit root URL
     * @throws IOException if the checking failed
     */
    protected URL determinePersistenceUnitRootUrl(Resource resource) throws IOException {
        URL originalURL = resource.getURL();
        String urlToString = originalURL.toExternalForm();

        // If we get an archive, simply return the jar URL (section 6.2 from the JPA spec)
        if (ResourceUtils.isJarURL(originalURL)) {
            return ResourceUtils.extractJarFileURL(originalURL);
        }

        else {
            // check META-INF folder
            if (!urlToString.contains(META_INF)) {
                if (logger.isInfoEnabled()) {
                    logger.info(resource.getFilename()
                            + " should be located inside META-INF directory; cannot determine persistence unit root URL for "
                            + resource);
                }
                return null;
            }
            if (urlToString.lastIndexOf(META_INF) == urlToString.lastIndexOf('/') - (1 + META_INF.length())) {
                if (logger.isInfoEnabled()) {
                    logger.info(resource.getFilename()
                            + " is not located in the root of META-INF directory; cannot determine persistence unit root URL for "
                            + resource);
                }
                return null;
            }

            String persistenceUnitRoot = urlToString.substring(0, urlToString.lastIndexOf(META_INF));
            return new URL(persistenceUnitRoot);
        }
    }

    /**
     * Parse the unit info DOM element.
     */
    protected SpringPersistenceUnitInfo parsePersistenceUnitInfo(Element persistenceUnit, String version)
            throws IOException { // JC: Changed
        SpringPersistenceUnitInfo unitInfo = new SpringPersistenceUnitInfo();

        // set JPA version (1.0 or 2.0)
        unitInfo.setPersistenceXMLSchemaVersion(version);

        // set unit name
        logger.info(
                "apdplatspring jpa1(1. Start to execute custom modifications  of APDPlat for Spring JPA )");
        String unitName = persistenceUnit.getAttribute(UNIT_NAME).trim();
        logger.info("??(Content of placeholder is): " + unitName);
        //${}??
        unitName = PropertyHolder.getProperty(unitName.substring(2, unitName.length() - 1));
        logger.info("???(Content of config file related to placeholder is): "
                + unitName);
        unitInfo.setPersistenceUnitName(unitName);

        // set transaction type
        String txType = persistenceUnit.getAttribute(TRANSACTION_TYPE).trim();
        if (StringUtils.hasText(txType)) {
            unitInfo.setTransactionType(PersistenceUnitTransactionType.valueOf(txType));
        }

        // data-source
        String jtaDataSource = DomUtils.getChildElementValueByTagName(persistenceUnit, JTA_DATA_SOURCE);
        if (StringUtils.hasText(jtaDataSource)) {
            unitInfo.setJtaDataSource(this.dataSourceLookup.getDataSource(jtaDataSource.trim()));
        }

        String nonJtaDataSource = DomUtils.getChildElementValueByTagName(persistenceUnit, NON_JTA_DATA_SOURCE);
        if (StringUtils.hasText(nonJtaDataSource)) {
            unitInfo.setNonJtaDataSource(this.dataSourceLookup.getDataSource(nonJtaDataSource.trim()));
        }

        // provider
        String provider = DomUtils.getChildElementValueByTagName(persistenceUnit, PROVIDER);
        if (StringUtils.hasText(provider)) {
            unitInfo.setPersistenceProviderClassName(provider.trim());
        }

        // exclude unlisted classes
        Element excludeUnlistedClasses = DomUtils.getChildElementByTagName(persistenceUnit,
                EXCLUDE_UNLISTED_CLASSES);
        if (excludeUnlistedClasses != null) {
            unitInfo.setExcludeUnlistedClasses(true);
        }

        // set JPA 2.0 shared cache mode
        String cacheMode = DomUtils.getChildElementValueByTagName(persistenceUnit, SHARED_CACHE_MODE);
        if (StringUtils.hasText(cacheMode)) {
            unitInfo.setSharedCacheModeName(cacheMode);
        }

        // set JPA 2.0 validation mode
        String validationMode = DomUtils.getChildElementValueByTagName(persistenceUnit, VALIDATION_MODE);
        if (StringUtils.hasText(validationMode)) {
            unitInfo.setValidationModeName(validationMode);
        }

        parseMappingFiles(persistenceUnit, unitInfo);
        parseJarFiles(persistenceUnit, unitInfo);
        parseClass(persistenceUnit, unitInfo);
        parseProperty(persistenceUnit, unitInfo);

        return unitInfo;
    }

    /**
     * Parse the <code>property</code> XML elements.
     */
    @SuppressWarnings("unchecked")
    protected void parseProperty(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
        Element propRoot = DomUtils.getChildElementByTagName(persistenceUnit, PROPERTIES);
        if (propRoot == null) {
            return;
        }
        List<Element> properties = DomUtils.getChildElementsByTagName(propRoot, "property");
        for (Element property : properties) {
            String name = property.getAttribute("name");
            String value = property.getAttribute("value");
            unitInfo.addProperty(name, value);
        }
    }

    /**
     * Parse the <code>class</code> XML elements.
     */
    @SuppressWarnings("unchecked")
    protected void parseClass(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
        List<Element> classes = DomUtils.getChildElementsByTagName(persistenceUnit, MANAGED_CLASS_NAME);
        for (Element element : classes) {
            String value = DomUtils.getTextValue(element).trim();
            if (StringUtils.hasText(value))
                unitInfo.addManagedClassName(value);
        }
    }

    /**
     * Parse the <code>jar-file</code> XML elements.
     */
    @SuppressWarnings("unchecked")
    protected void parseJarFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) throws IOException {
        List<Element> jars = DomUtils.getChildElementsByTagName(persistenceUnit, JAR_FILE_URL);
        for (Element element : jars) {
            logger.info(
                    "apdplatspring jpa2(2. Start to execute custom modifications  of APDPlat for Spring JPA )");
            String jarHolder = DomUtils.getTextValue(element).trim();
            if (jarHolder == null || "".equals(jarHolder.trim())) {
                continue;
            }
            logger.info("??(Content of placeholder is): " + jarHolder);
            //${}??
            String realJars = PropertyHolder.getProperty(jarHolder.substring(2, jarHolder.length() - 1));
            logger.info(
                    "???(Content of config file related to placeholder is): "
                            + realJars);
            String[] jarArray = realJars.split(",");
            for (String jar : jarArray) {
                if (StringUtils.hasText(jar)) {
                    FileSystemResource resource = new FileSystemResource(FileUtils.getAbsolutePath(jar));
                    unitInfo.addJarFileUrl(resource.getURL());
                }
            }
        }
    }

    /**
     * Parse the <code>mapping-file</code> XML elements.
     */
    @SuppressWarnings("unchecked")
    protected void parseMappingFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
        List<Element> files = DomUtils.getChildElementsByTagName(persistenceUnit, MAPPING_FILE_NAME);
        for (Element element : files) {
            String value = DomUtils.getTextValue(element).trim();
            if (StringUtils.hasText(value)) {
                unitInfo.addMappingFileName(value);
            }
        }
    }

}

/**
 * Subclass of {@link MutablePersistenceUnitInfo} that adds instrumentation hooks based on
 * Spring's {@link org.springframework.instrument.classloading.LoadTimeWeaver} abstraction.
 *
 * <p>This class is restricted to package visibility, in contrast to its superclass.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Costin Leau
 * @since 2.0
 * @see PersistenceUnitManager
 */
class SpringPersistenceUnitInfo extends MutablePersistenceUnitInfo {

    private static final String DEFAULT_SHARED_CACHE_MODE_NAME = "UNSPECIFIED";

    private static final String DEFAULT_VALIDATION_MODE_NAME = "AUTO";

    private String sharedCacheModeName = DEFAULT_SHARED_CACHE_MODE_NAME;

    private String validationModeName = DEFAULT_VALIDATION_MODE_NAME;

    private LoadTimeWeaver loadTimeWeaver;

    private ClassLoader classLoader;

    public void setSharedCacheModeName(String sharedCacheModeName) {
        this.sharedCacheModeName = (StringUtils.hasLength(sharedCacheModeName) ? sharedCacheModeName
                : DEFAULT_SHARED_CACHE_MODE_NAME);
    }

    public String getSharedCacheModeName() {
        return this.sharedCacheModeName;
    }

    public void setValidationModeName(String validationModeName) {
        this.validationModeName = (StringUtils.hasLength(validationModeName) ? validationModeName
                : DEFAULT_VALIDATION_MODE_NAME);
    }

    public String getValidationModeName() {
        return this.validationModeName;
    }

    /**
     * Initialize this PersistenceUnitInfo with the LoadTimeWeaver SPI interface
     * used by Spring to add instrumentation to the current class loader.
     */
    public void init(LoadTimeWeaver loadTimeWeaver) {
        Assert.notNull(loadTimeWeaver, "LoadTimeWeaver must not be null");
        this.loadTimeWeaver = loadTimeWeaver;
        this.classLoader = loadTimeWeaver.getInstrumentableClassLoader();
    }

    /**
     * Initialize this PersistenceUnitInfo with the current class loader
     * (instead of with a LoadTimeWeaver).
     */
    public void init(ClassLoader classLoader) {
        Assert.notNull(classLoader, "ClassLoader must not be null");
        this.classLoader = classLoader;
    }

    /**
     * This implementation returns the LoadTimeWeaver's instrumentable ClassLoader,
     * if specified.
     */
    @Override
    public ClassLoader getClassLoader() {
        return this.classLoader;
    }

    /**
     * This implementation delegates to the LoadTimeWeaver, if specified.
     */
    @Override
    public void addTransformer(ClassTransformer classTransformer) {
        if (this.loadTimeWeaver == null) {
            throw new IllegalStateException("Cannot apply class transformer without LoadTimeWeaver specified");
        }
        this.loadTimeWeaver.addTransformer(new ClassFileTransformerAdapter(classTransformer));
    }

    /**
     * This implementation delegates to the LoadTimeWeaver, if specified.
     */
    @Override
    public ClassLoader getNewTempClassLoader() {
        ClassLoader tcl = (this.loadTimeWeaver != null ? this.loadTimeWeaver.getThrowawayClassLoader()
                : new SimpleThrowawayClassLoader(this.classLoader));
        String packageToExclude = getPersistenceProviderPackageName();
        if (packageToExclude != null && tcl instanceof DecoratingClassLoader) {
            ((DecoratingClassLoader) tcl).excludePackage(packageToExclude);
        }
        return tcl;
    }

}

/**
 * Simple adapter that implements the <code>java.lang.instrument.ClassFileTransformer</code>
 * interface based on a JPA ClassTransformer which a JPA PersistenceProvider asks the
 * PersistenceUnitInfo to install in the current runtime.
 *
 * @author Rod Johnson
 * @since 2.0
 * @see javax.persistence.spi.PersistenceUnitInfo#addTransformer(javax.persistence.spi.ClassTransformer)
 */
class ClassFileTransformerAdapter implements ClassFileTransformer {

    private static final Log logger = LogFactory.getLog(ClassFileTransformerAdapter.class);

    private final ClassTransformer classTransformer;

    public ClassFileTransformerAdapter(ClassTransformer classTransformer) {
        Assert.notNull(classTransformer, "ClassTransformer must not be null");
        this.classTransformer = classTransformer;
    }

    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) {

        try {
            byte[] transformed = this.classTransformer.transform(loader, className, classBeingRedefined,
                    protectionDomain, classfileBuffer);
            if (transformed != null && logger.isDebugEnabled()) {
                logger.debug("Transformer of class [" + this.classTransformer.getClass().getName()
                        + "] transformed class [" + className + "]; bytes in=" + classfileBuffer.length
                        + "; bytes out=" + transformed.length);
            }
            return transformed;
        } catch (ClassCircularityError ex) {
            logger.error("Error weaving class [" + className + "] with " + "transformer of class ["
                    + this.classTransformer.getClass().getName() + "]", ex);
            throw new IllegalStateException("Could not weave class [" + className + "]", ex);
        } catch (Throwable ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Error weaving class [" + className + "] with " + "transformer of class ["
                        + this.classTransformer.getClass().getName() + "]", ex);
            }
            // The exception will be ignored by the class loader, anyway...
            throw new IllegalStateException("Could not weave class [" + className + "]", ex);
        }
    }

    @Override
    public String toString() {
        return "Standard ClassFileTransformer wrapping JPA transformer: " + this.classTransformer;
    }

}