DataSource.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.tomcat.jdbc.pool;

import java.util.Hashtable;

import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.jmx.JmxUtil;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;


/**
 * A DataSource that can be instantiated through IoC and implements the DataSource interface since the DataSourceProxy
 * is used as a generic proxy. The DataSource simply wraps a {@link ConnectionPool} in order to provide a standard
 * interface to the user.
 */
public class DataSource extends DataSourceProxy implements javax.sql.DataSource, MBeanRegistration,
        org.apache.tomcat.jdbc.pool.jmx.ConnectionPoolMBean, javax.sql.ConnectionPoolDataSource {
    private static final Log log = LogFactory.getLog(DataSource.class);

    /**
     * Constructor for reflection only. A default set of pool properties will be created.
     */
    public DataSource() {
        super();
    }

    /**
     * Constructs a DataSource object wrapping a connection
     *
     * @param poolProperties The pool properties
     */
    public DataSource(PoolConfiguration poolProperties) {
        super(poolProperties);
    }

    /**
     * Unregisters the connection pool MBean before closing the pool, so a dead pool does not keep reporting stale
     * statistics under a live object name (and does not pin the closed pool, its credentials and its JDBC driver class
     * loader in the MBean server).<br>
     * {@inheritDoc}
     */
    @Override
    public synchronized void close(boolean all) {
        // This monitor serializes the whole registration lifecycle against the
        // closure: preRegister(), postRegister() and postDeregister() take it
        // too, and DataSourceProxy.close(boolean) and the publication step of
        // pCreatePool() below run under the same lock. A close therefore
        // cannot slip in beside an in-flight publication - between its read of
        // the registration name and the publish itself - where its own
        // unregister would find nothing to take back and the pool MBean would
        // then be published onto the dead pool under a live name that no later
        // close() or postDeregister() could ever take back.
        releasePoolMBean();
        super.close(all);
    }


    // The JMX operations below register the pool itself under the
    // tomcat.jdbc domain.
    /**
     * The JMX ObjectName for the connection pool MBean. The handle is checked,
     * cleared and released only under this data source's monitor.
     */
    protected volatile ObjectName oname = null;

    /**
     * Unregisters the underlying connection pool mbean.<br>
     * {@inheritDoc}
     */
    @Override
    public synchronized void postDeregister() {
        // Under the monitor that close() and the registration callbacks take,
        // so the container's deregistration and a concurrent close release the
        // pool MBean exactly once: whichever of them runs second finds the
        // handle already gone and unregisters nothing - not even a name some
        // third component has published in the meantime.
        releasePoolMBean();
    }

    /**
     * Releases the pool MBean that {@link #preRegister(MBeanServer, ObjectName)} published when the MBean server did
     * not complete this DataSource's own registration. Per the {@link MBeanRegistration} contract a {@code false}
     * {@code registrationDone} means that the registration phase failed, and after a failed registration the server
     * does not call {@link #postDeregister()}: this callback is the only chance to undo that side effect. Leaving the
     * pool MBean registered would keep a pool nobody manages reporting statistics and notifications, and would pin the
     * pool - with its properties, its credentials and its JDBC driver class loader - in the MBean server.<br>
     * {@inheritDoc}
     */
    @Override
    public synchronized void postRegister(Boolean registrationDone) {
        // The same check-and-clear-unregister of the registration handle as
        // postDeregister() and close(), under the same monitor: a handle this
        // bean still holds at a failed registration is exactly the publish
        // preRegister() made, since registerJmx() keeps a name only for an
        // accepted publish. Clearing it here also makes a later close() or
        // postDeregister() skip the unregister, so the cleanup is never
        // performed twice.
        if (!Boolean.TRUE.equals(registrationDone)) {
            releasePoolMBean();
        }
    }


    /**
     * no-op<br>
     * {@inheritDoc}
     */
    @Override
    public void preDeregister() throws Exception {
        // NOOP
    }

    /**
     * If the connection pool MBean exists, it will be registered during this operation.<br>
     * {@inheritDoc}
     */
    @Override
    public synchronized ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
        // Under the monitor close() takes, so the publication below cannot
        // overlap a closure: a close that ran completely between registerJmx()
        // reading the registration name and the server actually accepting the
        // publish would unregister a name the pool never took and close the
        // pool, and the publish would then register the dead pool's MBean
        // under a live name that no later close() or postDeregister() - which
        // only release a held handle - could ever take back.
        try {
            if (isJmxEnabled()) {
                this.oname = createObjectName(name);
                if (oname != null) {
                    registerJmx();
                }
            }
        } catch (MalformedObjectNameException x) {
            this.oname = null;
            log.error(MessageFactory.get("dataSource.unableToCreateObjectName"), x);
        } catch (RuntimeException x) {
            // Never break the caller's MBean registration because of
            // pool-side state (for instance a pool that has not been
            // created yet); preRegister throwing aborts the whole
            // registration in the MBean server.
            this.oname = null;
            log.error(MessageFactory.get("dataSource.unableToRegisterMBean"), x);
        }
        return name;
    }

    /**
     * Creates the ObjectName for the ConnectionPoolMBean object to be registered
     *
     * @param original the ObjectName for the DataSource
     *
     * @return the ObjectName for the ConnectionPoolMBean
     *
     * @throws MalformedObjectNameException Invalid object name
     */
    public ObjectName createObjectName(ObjectName original) throws MalformedObjectNameException {
        String domain = ConnectionPool.POOL_JMX_DOMAIN;
        Hashtable<String, String> properties = original.getKeyPropertyList();
        String origDomain = original.getDomain();
        properties.put("type", "ConnectionPool");
        properties.put("class", this.getClass().getName());
        if (original.getKeyProperty("path") != null || properties.get("context") != null) {
            // This ensures that if the registration came from tomcat, we're not losing
            // the unique domain, but putting that into as an engine attribute
            properties.put("engine", origDomain);
        }
        ObjectName name = new ObjectName(domain, properties);
        return name;
    }

    /**
     * Registers the ConnectionPoolMBean under a unique name based on the ObjectName for the DataSource. A pool that has
     * not been created yet (or was already closed) has no MBean to publish; the registration is then skipped rather
     * than failing, since the caller of {@link #preRegister(MBeanServer, ObjectName)} must not see an exception over
     * pool-side lifecycle state. A publish the MBean server refused - because the name is already in use by another
     * MBean, for instance - is likewise not this pool's registration.
     */
    protected synchronized void registerJmx() {
        // Runs under this data source's monitor - from preRegister() or a
        // direct call - the monitor close(boolean) also takes, so no closure
        // can interleave with the publish below.
        final ConnectionPool p = pool;
        // Read the pool's JMX object only once: a pool that was closed before
        // this registration has given it up, and one read that saw it must not
        // be followed by another that observes the null - a registration
        // attempt against that null would only fail inside the helper, with a
        // spurious error log
        final org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = p == null ? null : p.getJmxPool();
        if (jmx == null) {
            log.warn(MessageFactory.get("dataSource.mbeanNotRegistered", oname));
            // oname is the handle postDeregister() unregisters; do not keep
            // one that was never registered
            this.oname = null;
            return;
        }
        // oname is only kept for an actually published MBean: the helper
        // hands back the name of its own publish, or null when the server
        // refused it, so a name a foreign MBean owns is never released as
        // if it were this pool's registration
        this.oname = JmxUtil.registerJmx(oname, null, jmx);
        if (this.oname != null) {
            // A connection publishes itself below this name when its physical
            // connection is established; the connections the pool was already
            // holding could not do that while this name did not exist yet, so
            // they are published now. A publish the server refused registers
            // nothing of its own and has no connections to publish either.
            p.registerConnectionMBeans();
        }
    }

    /**
     * Unregisters the ConnectionPoolMBean published under the given name. The
     * name is the registration handle captured by the caller, which holds this
     * data source's monitor: the handle is checked, cleared and released as
     * one step there, so it never reaches this method twice and a name this
     * data source never published is never released.
     *
     * @param name the ObjectName the pool MBean was published under, or
     *            {@code null} to release nothing
     */
    protected void unregisterJmx(ObjectName name) {
        JmxUtil.unregisterJmx(name);
    }

    /**
     * Releases the registration handle and unregisters the pool MBean
     * published under the name it points to, as one step under this data
     * source's monitor. Every path that gives the handle up runs through
     * here - {@link #close(boolean)}, the {@link #postRegister(Boolean)} of a
     * failed registration and the container's {@link #postDeregister()} - so
     * of all of them the release is performed at most once: whichever runs
     * second finds the handle gone and unregisters nothing.
     */
    private void releasePoolMBean() {
        final ObjectName name = oname;
        oname = null;
        if (name != null) {
            unregisterJmx(name);
        }
    }
}