Package bitronix.tm.resource

Source Code of bitronix.tm.resource.ResourceLoader

/*
* Bitronix Transaction Manager
*
* Copyright (c) 2010, Bitronix Software.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package bitronix.tm.resource;

import bitronix.tm.TransactionManagerServices;
import bitronix.tm.utils.InitializationException;
import bitronix.tm.utils.PropertyUtils;
import bitronix.tm.utils.Service;
import bitronix.tm.utils.ClassLoaderUtils;
import bitronix.tm.resource.common.XAResourceProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jms.XAConnectionFactory;
import javax.sql.XADataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;

/**
* XA resources pools configurator & loader.
* <p>{@link ResourceLoader} relies on the optional <code>bitronix.tm.resource.configuration</code> propery to load the
* JDBC datasources ({@link bitronix.tm.resource.jdbc.PoolingDataSource}) and JMS connection factories
* ({@link bitronix.tm.resource.jms.PoolingConnectionFactory}) configuration file and create the resources.</p>
* <p>When <code>bitronix.tm.resource.configuration</code> is not specified, ResourceLoader is disabled and resources
* should be manually created.</p>
*
* @author lorban
*/
public class ResourceLoader implements Service {

    private final static Logger log = LoggerFactory.getLogger(ResourceLoader.class);

    private final static String JDBC_RESOURCE_CLASSNAME = "bitronix.tm.resource.jdbc.PoolingDataSource";
    private final static String JMS_RESOURCE_CLASSNAME = "bitronix.tm.resource.jms.PoolingConnectionFactory";

    private Map resourcesByUniqueName = Collections.EMPTY_MAP;

    public ResourceLoader() {
    }

    /**
     * Get a Map with the configured uniqueName as key and {@link XAResourceProducer} as value.
     * @return a Map using the uniqueName as key and {@link XAResourceProducer} as value.
     */
    public Map getResources() {
        return resourcesByUniqueName;
    }

    /**
     * Initialize the ResourceLoader and load the resources configuration file specified in
     * <code>bitronix.tm.resource.configuration</code> property.
     * @return the number of resources which failed to initialize.
     */
    public int init() {
        String filename = TransactionManagerServices.getConfiguration().getResourceConfigurationFilename();
        if (filename != null) {
            if (!new File(filename).exists())
                throw new ResourceConfigurationException("cannot find resources configuration file '" + filename +"', missing or invalid value of property 'bitronix.tm.resource.configuration'");
            log.info("reading resources configuration from " + filename);
            return init(filename);
        }
        else {
            if (log.isDebugEnabled()) log.debug("no resource configuration file specified");
            return 0;
        }
    }

    public synchronized void shutdown() {
        if (log.isDebugEnabled()) log.debug("resource loader has registered " + resourcesByUniqueName.entrySet().size() + " resource(s), unregistering them now");
        Iterator it = resourcesByUniqueName.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            XAResourceProducer producer = (XAResourceProducer) entry.getValue();
            if (log.isDebugEnabled()) log.debug("closing " + producer);
            try {
                producer.close();
            } catch (Exception ex) {
                log.warn("error closing resource " + producer, ex);
            }
        }
        resourcesByUniqueName.clear();
    }

    /*
     * Internal impl.
     */

    /**
     * Create an unitialized {@link XAResourceProducer} implementation which depends on the XA resource class name.
     * @param xaResourceClassName an XA resource class name.
     * @return a {@link XAResourceProducer} implementation.
     * @throws ClassNotFoundException if the {@link XAResourceProducer} cannot be instanciated.
     * @throws IllegalAccessException if the {@link XAResourceProducer} cannot be instanciated.
     * @throws InstantiationException if the {@link XAResourceProducer} cannot be instanciated.
     */
    private static XAResourceProducer instanciate(String xaResourceClassName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class clazz = ClassLoaderUtils.loadClass(xaResourceClassName);

        // resource classes are instanciated via reflection so that there is no hard class binding between this internal
        // transaction manager service and 3rd party libraries like the JMS ones.
        // This allows using the TM with a 100% JDBC application without requiring JMS libraries.

        if (XADataSource.class.isAssignableFrom(clazz)) {
            return (XAResourceProducer) ClassLoaderUtils.loadClass(JDBC_RESOURCE_CLASSNAME).newInstance();
        }
        else if (XAConnectionFactory.class.isAssignableFrom(clazz)) {
            return (XAResourceProducer) ClassLoaderUtils.loadClass(JMS_RESOURCE_CLASSNAME).newInstance();
        }
        else
            return null;
    }

    /**
     * Read the resources properties file and create {@link XAResourceProducer} accordingly.
     * @param propertiesFilename the name of the properties file to load.
     * @return the number of resources which failed to initialize.
     */
    private int init(String propertiesFilename) {
        try {
            FileInputStream fis = null;
            Properties properties;
            try {
                fis = new FileInputStream(propertiesFilename);
                properties = new Properties();
                properties.load(fis);
            } finally {
                if (fis != null) fis.close();
            }

            return initXAResourceProducers(properties);
        } catch (IOException ex) {
            throw new InitializationException("cannot create resource loader", ex);
        }
    }

    /**
     * Initialize {@link XAResourceProducer}s given a set of properties.
     * @param properties the properties to use for initialization.
     * @return the number of resources which failed to initialize.
     */
    int initXAResourceProducers(Properties properties) {
        Map entries = buildConfigurationEntriesMap(properties);
        int errorCount = 0;

        resourcesByUniqueName = new HashMap();
        for (Iterator it = entries.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            String uniqueName = (String) entry.getKey();
            List propertyPairs = (List) entry.getValue();
            XAResourceProducer producer = buildXAResourceProducer(uniqueName, propertyPairs);

            if (ResourceRegistrar.get(producer.getUniqueName()) != null) {
                if (log.isDebugEnabled()) log.debug("resource already registered, skipping it:" + producer.getUniqueName());
                continue;
            }

            if (log.isDebugEnabled()) log.debug("creating resource " + producer);
            try {
                producer.init();
            } catch (ResourceConfigurationException ex) {
                log.warn("unable to create resource with unique name " + producer.getUniqueName(), ex);
                producer.close();
                errorCount++;
            }

            resourcesByUniqueName.put(producer.getUniqueName(), producer);
        }

        return errorCount;
    }

    /**
     * Create a map using the configured resource name as the key and a List of PropertyPair objects as the value.
     * @param properties object to analyze.
     * @return the built map.
     */
    private Map buildConfigurationEntriesMap(Properties properties) {
        Map entries = new HashMap();
        Iterator it = properties.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();

            if (key.startsWith("resource.")) {
                String[] keyParts = key.split("\\.");
                if (keyParts.length < 3) {
                    log.warn("ignoring invalid entry in configuration file: " + key);
                    continue;
                }
                String configuredName = keyParts[1];
                String propertyName = keyParts[2];
                if (keyParts.length > 3) {
                    for (int i=3; i < keyParts.length; i++) {
                        propertyName += "." + keyParts[i];
                    }
                }

                List pairs = (List) entries.get(configuredName);
                if (pairs == null) {
                    pairs = new ArrayList();
                    entries.put(configuredName, pairs);
                }

                pairs.add(new PropertyPair(propertyName, value));
            }
        }
        return entries;
    }

    /**
     * Build a populated {@link XAResourceProducer} out of a list of property pairs and the config name.
     * @param configuredName index name of the config file.
     * @param propertyPairs the properties attached to this index.
     * @return a populated {@link XAResourceProducer}.
     * @throws ResourceConfigurationException if the {@link XAResourceProducer} cannot be built.
     */
    private XAResourceProducer buildXAResourceProducer(String configuredName, List propertyPairs) throws ResourceConfigurationException {
        String lastPropertyName = "className";
        try {
            XAResourceProducer producer = createBean(configuredName, propertyPairs);

            for (int i = 0; i < propertyPairs.size(); i++) {
                PropertyPair propertyPair = (PropertyPair) propertyPairs.get(i);
                lastPropertyName = propertyPair.getName();
                String propertyValue = propertyPair.getValue();

                PropertyUtils.setProperty(producer, lastPropertyName, propertyValue);
            }
            if (producer.getUniqueName() == null)
                throw new ResourceConfigurationException("missing mandatory property [uniqueName] of resource [" + configuredName + "] in resources configuration file");

            return producer;
        } catch (ResourceConfigurationException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new ResourceConfigurationException("cannot configure resource for configuration entries with name [" + configuredName + "]" + " - failing property is [" + lastPropertyName + "]", ex);
        }
    }

    /**
     * Create an unpopulated, uninitialized {@link XAResourceProducer} instance depending on the className value.
     * @param configuredName the properties configured name.
     * @param propertyPairs a list of {@link PropertyPair}s.
     * @return a {@link XAResourceProducer}.
     * @throws ClassNotFoundException if the {@link XAResourceProducer} cannot be instanciated.
     * @throws IllegalAccessException if the {@link XAResourceProducer} cannot be instanciated.
     * @throws InstantiationException if the {@link XAResourceProducer} cannot be instanciated.
     */
    private XAResourceProducer createBean(String configuredName, List propertyPairs) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        for (int i = 0; i < propertyPairs.size(); i++) {
            PropertyPair propertyPair = (PropertyPair) propertyPairs.get(i);
            if (propertyPair.getName().equals("className")) {
                String className = propertyPair.getValue();
                XAResourceProducer producer = instanciate(className);
                if (producer == null)
                    throw new ResourceConfigurationException("property [className] " +
                            "of resource [" + configuredName + "] in resources configuration file " +
                            "must be the name of a class implementing either javax.sql.XADataSource or javax.jms.XAConnectionFactory");
                return producer;
            }
        }
        throw new ResourceConfigurationException("missing mandatory property [className] for resource [" + configuredName + "] in resources configuration file");
    }


    private class PropertyPair {
        private String name;
        private String value;

        public PropertyPair(String key, String value) {
            this.name = key;
            this.value = value;
        }

        public String getName() {
            return name;
        }

        public String getValue() {
            return value;
        }

        public String toString() {
            return name + "/" + value;
        }
    }

}
TOP

Related Classes of bitronix.tm.resource.ResourceLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.