View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.apache.myfaces.orchestra.connectionManager;
    20
    21  import javax.naming.Context;
    22  import javax.naming.InitialContext;
    23  import javax.naming.NamingException;
    24  import javax.sql.DataSource;
    25  import java.io.PrintWriter;
    26  import java.sql.Connection;
    27  import java.sql.SQLException;
    28  import java.util.HashSet;
    29  import java.util.Set;
    30
    31  /**
    32   * Manage all borrowed connections and hand out
    33   * {@link org.apache.myfaces.orchestra.connectionManager.DisconnectableConnection}
    34   * objects so that we can close them again after the HTTP request has been finished.
    35   * <p>
    36   * This datasource can be configured as a "wrapper" for a real datasource. When a connection is
    37   * requested from this object, a proxy is returned that simply forwards all calls transparently
    38   * to the real connection. This manager keeps track of all the Connections borrowed by each
    39   * thread. At some point (eg from a servlet filter) this object can be asked to check for
    40   * unreturned connections held by the current thread. If any exist then the real connection
    41   * is returned to the underlying datasource and the proxy's connection reference is set to null.
    42   * This ensures that a thread cannot leak real database connections.
    43   * <p>
    44   * Of course all code should return its connections; this is only a workaround/hack useful when the
    45   * real problem cannot be fixed. This is particularly useful for JPA implementations that do not free
    46   * their connection again after a lazy-init.
    47   * <p>
    48   * If a proxy's underlying connection has been returned to the database (either via the
    49   * leak-detection, or by explicitly calling close) then invoking any method on the proxy
    50   * will transparently cause a new connection to be retrieved from the underlying datasource.
    51   * This means that a Connection returned by this datasource works somewhat differently than
    52   * a normal one: for a normal connection, close() followed by prepareStatement() would cause
    53   * an exception to be thrown, but works when this datasource is used.
    54   *
    55   * @see org.apache.myfaces.orchestra.connectionManager.DisconnectableConnection
    56   */
    57  public class ConnectionManagerDataSource implements DataSource
    58  {
    59      private DataSource dataSource;
    60      private String jndiName;
    61
    62      // List of connections that have been borrowed by this thread but not returned.
    63      // When using a threadpool, it is required that the releaseAllBorrowedConnections
    64      // method be called before the thread is returned to the pool; that ensures this
    65      // threadlocal is reset to null.
    66      private static ThreadLocal borrowedConnections = new ThreadLocal()
    67      {
    68          protected Object initialValue()
    69          {
    70              return new HashSet();
    71          }
    72      };
    73
    74      public ConnectionManagerDataSource()
    75      {
    76      }
    77
    78      void onAfterBorrowConnection(Connection con)
    79      {
    80          ((Set) borrowedConnections.get()).add(con);
    81      }
    82
    83      void onAfterReleaseConnection(Connection con)
    84      {
    85          ((Set) borrowedConnections.get()).remove(con);
    86      }
    87
    88      /**
    89       * If the calling thread has allocated connections via this datasource, then return the
    90       * underlying real connections to the underlying datasource.
    91       * <p>
    92       * To code that holds references to the proxy connection returned by this datasource,
    93       * this operation is generally transparent. They continue to hold a reference to the
    94       * proxy, and if a method is ever called on that proxy in the future then the proxy
    95       * will transparently allocate a new underlying Connection at that time.
    96       * <p>
    97       * This is expected to be called just before a thread is returned to a threadpool,
    98       * eg via a ServletFilter just before returning from processing a request.
    99       */
    100     public static void releaseAllBorrowedConnections()
    101     {
    102         DisconnectableConnection[] connections = new DisconnectableConnection[((Set) borrowedConnections.get()).size()];
    103         ((Set) borrowedConnections.get()).toArray(connections);
    104
    105         for (int i = 0; i<connections.length; i++)
    106         {
    107             DisconnectableConnection connection = connections[i];
    108             connection.disconnect();
    109         }
    110
    111         ((Set) borrowedConnections.get()).clear();
    112     }
    113
    114     /**
    115      * Set the underlying datasource via an explicit call.
    116      * See also method setJndiName.
    117      */
    118     public void setDataSource(DataSource dataSource)
    119     {
    120         this.dataSource = dataSource;
    121     }
    122
    123     /**
    124      * Return the underlying datasource for this wrapper.
    125      * <p>
    126      * If method setJndiName was used to specify the datasource, then it is retrieved
    127      * from JNDI if necessary.
    128      *
    129      * @throws IllegalArgumentException if neither setDataSource nor setJndiName was called.
    130      * @throws IllegalArgumentException if an invalid jndi name was specified.
    131      */
    132     public DataSource getDataSource()
    133     {
    134         if (dataSource != null)
    135         {
    136             return dataSource;
    137         }
    138
    139         try
    140         {
    141             Context ctx = new InitialContext();
    142             dataSource = (DataSource) ctx.lookup(jndiName);
    143         }
    144         catch (NamingException e)
    145         {
    146             throw (IllegalArgumentException) new IllegalArgumentException(jndiName).initCause(e);
    147         }
    148
    149         return dataSource;
    150     }
    151
    152     /**
    153      * Specify that the underlying datasource should be retrieved via JNDI.
    154      */
    155     public void setJndiName(String jndiName)
    156     {
    157         this.jndiName = jndiName;
    158     }
    159
    160     /**
    161      * Return a proxy that wraps a connection of the underlying datasource.
    162      */
    163     public Connection getConnection() throws SQLException
    164     {
    165         return DisconnectableConnectionFactory.create(this);
    166     }
    167
    168     /**
    169      * Not supported. Always throws UnsupportedOperationException.
    170      */
    171     public Connection getConnection(String username, String password) throws SQLException
    172     {
    173         throw new UnsupportedOperationException();
    174     }
    175
    176     /** @inheritDoc */
    177     public PrintWriter getLogWriter() throws SQLException
    178     {
    179         return getDataSource().getLogWriter();
    180     }
    181
    182     /** @inheritDoc */
    183     public void setLogWriter(PrintWriter out) throws SQLException
    184     {
    185         getDataSource().setLogWriter(out);
    186     }
    187
    188     /** @inheritDoc */
    189     public void setLoginTimeout(int seconds) throws SQLException
    190     {
    191         getDataSource().setLoginTimeout(seconds);
    192     }
    193
    194     /** @inheritDoc */
    195     public int getLoginTimeout() throws SQLException
    196     {
    197         return getDataSource().getLoginTimeout();
    198     }
    199
    200     /**
    201      * Always throws UnsupportedOperationException.
    202      * <p>
    203      * Note that this method was only introduced in java 1.6, and therefore
    204      * cannot be implemented on platforms earlier than this without using
    205      * reflection. Orchestra supports pre-1.6 JVMs, and this is a very
    206      * rarely used method so currently no support is offered for this
    207      * method.
    208      */
    209     public Object unwrap(Class iface) throws SQLException
    210     {
    211         throw new UnsupportedOperationException();
    212         /*
    213         try
    214         {
    215             if (iface.isAssignableFrom(dataSource.getClass()))
    216             {
    217                 return dataSource;
    218             }
    219
    220             Method method = dataSource.getClass().getMethod("unwrap", new Class[]{Class.class});
    221             return method.invoke(dataSource, new Object[] { iface });
    222         }
    223         catch (NoSuchMethodException e)
    224         {
    225             throw new UnsupportedOperationException();
    226         }
    227         catch (IllegalAccessException e)
    228         {
    229             throw new SQLException(e);
    230         }
    231         catch (InvocationTargetException e)
    232         {
    233             throw new SQLException(e);
    234         }
    235         */
    236     }
    237
    238     /**
    239      * Always throws UnsupportedOperationException.
    240      * See method unwrap.
    241      */
    242     public boolean isWrapperFor(Class iface) throws SQLException
    243     {
    244         throw new UnsupportedOperationException();
    245
    246         /*
    247         try
    248         {
    249             if (iface.isAssignableFrom(dataSource.getClass()))
    250             {
    251                 return true;
    252             }
    253             Method method = dataSource.getClass().getMethod("isWrapperFor", new Class[]{Class.class});
    254             return Boolean.TRUE.equals(method.invoke(dataSource, new Object[] { iface }));
    255         }
    256         catch (NoSuchMethodException e)
    257         {
    258             throw new UnsupportedOperationException();
    259         }
    260         catch (IllegalAccessException e)
    261         {
    262             throw new SQLException(e);
    263         }
    264         catch (InvocationTargetException e)
    265         {
    266             throw new SQLException(e);
    267         }
    268         */
    269     }
    270 }