001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    package org.apache.xalan.lib.sql;
019    
020    import java.lang.reflect.Method;
021    import java.sql.Connection;
022    import java.sql.SQLException;
023    import java.util.Properties;
024    
025    import javax.naming.InitialContext;
026    import javax.naming.NamingException;
027    
028    
029    /**
030     * A Connection Pool that wraps a JDBC datasource to provide connections.
031     * 
032     * An instance of this class is created by <code>XConnection</code> when it
033     * attempts to resolves a <code>ConnectionPool</code> name as a JNDI data source.
034     * 
035     * Most methods in this implementation do nothing since configuration is handled
036     * by the underlying JDBC datasource.  Users should always call
037     * <code>XConnection.close()</code> from their stylsheet to explicitely close
038     * their connection.  However, since there is no way to enforce this
039     * (Yikes!), it is recommended that a relatively short datasource timeout
040     * be used to prevent dangling connections.
041     */
042    public class JNDIConnectionPool implements ConnectionPool
043    {
044      
045      /**
046       * Reference to the datasource
047       */
048      protected Object jdbcSource = null;
049    
050      /** 
051       * To maintain Java 1.3 compatibility, we need to work with the
052       * DataSource class through Reflection. The getConnection method
053       * is one of the methods used, and there are two different flavors.
054       * 
055       */  
056      private Method getConnectionWithArgs = null;
057      private Method getConnection = null;
058      
059      
060      /**
061       * The unique jndi path for this datasource.
062       */
063      protected String jndiPath = null;
064      
065      /**
066       * User name for protected datasources.
067       */
068      protected String user = null;
069      
070      /**
071       * Password for protected datasources.
072       */
073      protected String pwd = null;
074      
075      /**
076       * Use of the default constructor requires the jndi path to be set via
077       * setJndiPath().
078       */
079      public JNDIConnectionPool() {  }
080      
081      /**
082       * Creates a connection pool with a specified JNDI path. 
083       * @param jndiDatasourcePath Complete path to the JNDI datasource
084       */
085      public JNDIConnectionPool(String jndiDatasourcePath)
086      {
087        jndiPath = jndiDatasourcePath.trim();
088      }
089      
090      /**
091       * Sets the path for the jndi datasource 
092       * @param jndiPath 
093       */
094      public void setJndiPath(String jndiPath)
095      {
096        this.jndiPath = jndiPath;
097      }
098    
099      /**
100       * Returns the path for the jndi datasource 
101       * @param jndiPath 
102       */
103      public String getJndiPath()
104      {
105        return jndiPath;
106      }
107    
108      /**
109       * Always returns true.
110       * This method was intended to indicate if the pool was enabled, however, in
111       * this implementation that is not relavant.
112       * @return 
113       */
114      public boolean isEnabled()
115      {
116        return true;
117      }
118    
119      /**
120       * Not implemented and will throw an Error if called.
121       * 
122       * Connection configuration is handled by the underlying JNDI DataSource.
123       * @param d 
124       */
125      public void setDriver(String d)
126      {
127        throw new Error(
128          "This method is not supported. " +
129          "All connection information is handled by the JDBC datasource provider");
130      }
131    
132      /**
133       * Not implemented and will throw an Error if called.
134       * 
135       * Connection configuration is handled by the underlying JNDI DataSource.
136       * @param d 
137       */
138      public void setURL(String url)
139      {
140        throw new Error(
141          "This method is not supported. " +
142          "All connection information is handled by the JDBC datasource provider");
143      }
144    
145      /**
146       * Intended to release unused connections from the pool.
147       * Does nothing in this implementation.
148       */
149      public void freeUnused()
150      {
151        //Do nothing - not an error to call this method
152      }
153    
154      /**
155       * Always returns false, indicating that this wrapper has no idea of what
156       * connections the underlying JNDI source is maintaining.
157       * @return 
158       */
159      public boolean hasActiveConnections()
160      {
161        return false;
162      }
163    
164      /**
165       * Sets the password for the connection.
166       * If the jndi datasource does not require a password (which is typical),
167       * this can be left null.
168       * @param p the password
169       */
170      public void setPassword(String p)
171      {
172        
173        if (p != null) p = p.trim();
174        if (p != null && p.length() == 0) p = null;
175        
176        pwd = p;
177      }
178    
179      /**
180       * Sets the user name for the connection.
181       * If the jndi datasource does not require a user name (which is typical),
182       * this can be left null.
183       * @param u the user name
184       */
185      public void setUser(String u)
186      {
187        
188        if (u != null) u = u.trim();
189        if (u != null && u.length() == 0) u = null;
190        
191        user = u;
192      }
193    
194      /**
195       * Returns a connection from the JDNI DataSource found at the JNDI Datasource
196       * path.
197       * 
198       * @return 
199       * @throws SQLException 
200       */
201      public Connection getConnection() throws SQLException
202      {
203        if (jdbcSource == null)
204        {
205          try
206          {
207            findDatasource();
208          }
209          catch (NamingException ne)
210          {
211            throw new SQLException(
212              "Could not create jndi context for " + 
213              jndiPath + " - " + ne.getLocalizedMessage());
214          }
215        }
216        
217        try
218        {
219          if (user != null || pwd != null)
220          {
221            Object arglist[] = { user, pwd }; 
222            return (Connection) getConnectionWithArgs.invoke(jdbcSource, arglist);
223          }
224          else
225          {
226            Object arglist[] = {}; 
227            return (Connection) getConnection.invoke(jdbcSource, arglist);
228          }
229        }
230        catch (Exception e)
231        {
232          throw new SQLException(
233            "Could not create jndi connection for " + 
234            jndiPath + " - " + e.getLocalizedMessage());
235        }
236        
237      }
238      
239      /**
240       * Internal method used to look up the datasource. 
241       * @throws NamingException 
242       */
243      protected void findDatasource() throws NamingException
244      {
245        try
246        {
247          InitialContext context = new InitialContext();
248          jdbcSource =  context.lookup(jndiPath);
249          
250          Class withArgs[] = { String.class, String.class };
251          getConnectionWithArgs = 
252            jdbcSource.getClass().getDeclaredMethod("getConnection", withArgs);
253          
254          Class noArgs[] = { };
255          getConnection = 
256            jdbcSource.getClass().getDeclaredMethod("getConnection", noArgs);
257          
258        }
259        catch (NamingException e)
260        {
261          throw e;
262        }
263        catch (NoSuchMethodException e)
264        {
265          // For simpleification, we will just throw a NamingException. We will only
266          // use the message part of the exception anyway.
267          throw new NamingException("Unable to resolve JNDI DataSource - " + e);
268        }
269      }
270    
271      public void releaseConnection(Connection con) throws SQLException
272      {
273        con.close();
274      }
275    
276      public void releaseConnectionOnError(Connection con) throws SQLException
277      {
278        con.close();
279      }
280    
281      /**
282       * Releases the reference to the jndi datasource.
283       * The original intention of this method was to actually turn the pool *off*.
284       * Since we are not managing the pool, we simply release our reference to
285       * the datasource.  Future calls to the getConnection will simply recreate
286       * the datasource.
287       * @param flag If false, the reference to the datasource is released.
288       */
289      public void setPoolEnabled(boolean flag)
290      {
291        if (! flag) jdbcSource = null;
292      }
293    
294      /**
295       * Ignored in this implementation b/c the pooling is determined by the jndi dataosource. 
296       * @param p
297       */
298      public void setProtocol(Properties p)
299      {
300        /* ignore - properties are determined by datasource */
301      }
302      
303      /**
304       * Ignored in this implementation b/c the pooling is determined by the jndi dataosource. 
305       * @param n 
306       */
307      public void setMinConnections(int n)
308      {
309        /* ignore - pooling is determined by datasource */
310      }
311    
312      /**
313       * A simple test to see if the jndi datasource exists.
314       * 
315       * Note that this test does not ensure that the datasource will return valid
316       * connections.
317       */
318      public boolean testConnection()
319      {
320        if (jdbcSource == null)
321        {
322          try
323          {
324            findDatasource();
325          }
326          catch (NamingException ne)
327          {
328            return false;
329          }
330        }
331        
332        return true;
333      }
334    
335    
336    
337    }