View Javadoc

1   package org.apache.torque.dsfactory;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Hashtable;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.StringTokenizer;
26  
27  import javax.naming.Context;
28  import javax.naming.InitialContext;
29  import javax.naming.NameAlreadyBoundException;
30  import javax.naming.NamingException;
31  import javax.sql.DataSource;
32  
33  import org.apache.commons.configuration.Configuration;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  import org.apache.torque.TorqueException;
39  
40  /***
41   * A factory that looks up the DataSource from JNDI.  It is also able
42   * to deploy the DataSource based on properties found in the
43   * configuration.
44   *
45   * This factory tries to avoid excessive context lookups to improve speed.
46   * The time between two lookups can be configured. The default is 0 (no cache).
47   *
48   * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
49   * @author <a href="mailto:thomas@vandahl.org">Thomas Vandahl</a>
50   * @version $Id: JndiDataSourceFactory.java 476550 2006-11-18 16:08:37Z tfischer $
51   */
52  public class JndiDataSourceFactory
53      extends AbstractDataSourceFactory
54  {
55      /***
56       * Key for the configuration which contains jndi properties.
57       */
58      public static final String JNDI_KEY = "jndi";
59  
60      /***
61       *  Key for the configuration property which contains the jndi path.
62       */
63      public static final String PATH_KEY = "path";
64  
65      /***
66       *  Key for the configuration property which contains the
67       *  time between two jndi lookups.
68       */
69      public static final String TIME_BETWEEN_LOOKUPS_KEY = "ttl";
70  
71      /***
72       * Key for the configuration which contains properties for a DataSource
73       * which should be bound into jndi.
74       */
75      public static final String DATASOURCE_KEY = "datasource";
76  
77      /***
78       *  Key for the configuration property which contains the class name
79       *  of the datasource to be bound into jndi.
80       */
81      public static final String CLASSNAME_KEY = "classname";
82  
83      /*** The log. */
84      private static Log log = LogFactory.getLog(JndiDataSourceFactory.class);
85  
86      /*** The path to get the resource from. */
87      private String path;
88      /*** The context to get the resource from. */
89      private Context ctx;
90  
91      /*** A locally cached copy of the DataSource */
92      private DataSource ds = null;
93  
94      /*** Time of last actual lookup action */
95      private long lastLookup = 0;
96  
97      /*** Time between two lookups */
98      private long ttl = 0; // ms
99  
100     /***
101      * @see org.apache.torque.dsfactory.DataSourceFactory#getDataSource
102      */
103     public DataSource getDataSource() throws TorqueException
104     {
105         long time = System.currentTimeMillis();
106 
107         if (ds == null || time - lastLookup > ttl)
108         {
109             try
110             {
111                 ds = ((DataSource) ctx.lookup(path));
112                 lastLookup = time;
113             }
114             catch (Exception e)
115             {
116                 throw new TorqueException(e);
117             }
118         }
119 
120         return ds;
121     }
122 
123     /***
124      * @see org.apache.torque.dsfactory.DataSourceFactory#initialize
125      */
126     public void initialize(Configuration configuration) throws TorqueException
127     {
128         super.initialize(configuration);
129 
130         initJNDI(configuration);
131         initDataSource(configuration);
132     }
133 
134     /***
135      * Initializes JNDI.
136      *
137      * @param configuration where to read the settings from
138      * @throws TorqueException if a property set fails
139      */
140     private void initJNDI(Configuration configuration) throws TorqueException
141     {
142         log.debug("Starting initJNDI");
143 
144         Configuration c = configuration.subset(JNDI_KEY);
145         if (c == null || c.isEmpty())
146         {
147             throw new TorqueException(
148                 "JndiDataSourceFactory requires a jndi "
149                     + "path property to lookup the DataSource in JNDI.");
150         }
151 
152         try
153         {
154             Hashtable env = new Hashtable();
155             for (Iterator i = c.getKeys(); i.hasNext();)
156             {
157                 String key = (String) i.next();
158                 if (key.equals(PATH_KEY))
159                 {
160                     path = c.getString(key);
161                     if (log.isDebugEnabled())
162                     {
163                         log.debug("JNDI path: " + path);
164                     }
165                 }
166                 else if (key.equals(TIME_BETWEEN_LOOKUPS_KEY))
167                 {
168                     ttl = c.getLong(key, ttl);
169                     if (log.isDebugEnabled())
170                     {
171                         log.debug("Time between context lookups: " + ttl);
172                     }
173                 }
174                 else
175                 {
176                     String value = c.getString(key);
177                     env.put(key, value);
178                     if (log.isDebugEnabled())
179                     {
180                         log.debug("Set jndi property: " + key + "=" + value);
181                     }
182                 }
183             }
184 
185             ctx = new InitialContext(env);
186             log.debug("Created new InitialContext");
187             debugCtx(ctx);
188         }
189         catch (Exception e)
190         {
191             log.error("", e);
192             throw new TorqueException(e);
193         }
194     }
195 
196     /***
197      * Initializes the DataSource.
198      *
199      * @param configuration where to read the settings from
200      * @throws TorqueException if a property set fails
201      */
202     private void initDataSource(Configuration configuration)
203         throws TorqueException
204     {
205         log.debug("Starting initDataSource");
206         try
207         {
208             Object dataSource = null;
209 
210             Configuration c = configuration.subset(DATASOURCE_KEY);
211             if (c != null)
212             {
213                 for (Iterator i = c.getKeys(); i.hasNext();)
214                 {
215                     String key = (String) i.next();
216                     if (key.equals(CLASSNAME_KEY))
217                     {
218                         String classname = c.getString(key);
219                         if (log.isDebugEnabled())
220                         {
221                             log.debug("Datasource class: " + classname);
222                         }
223 
224                         Class dsClass = Class.forName(classname);
225                         dataSource = dsClass.newInstance();
226                     }
227                     else
228                     {
229                         if (dataSource != null)
230                         {
231                             if (log.isDebugEnabled())
232                             {
233                                 log.debug("Setting datasource property: " + key);
234                             }
235                             setProperty(key, c, dataSource);
236                         }
237                         else
238                         {
239                             log.error("Tried to set property " + key
240                                     + " without Datasource definition!");
241                         }
242                     }
243                 }
244             }
245 
246             if (dataSource != null)
247             {
248                 bindDStoJndi(ctx, path, dataSource);
249             }
250         }
251         catch (Exception e)
252         {
253             log.error("", e);
254             throw new TorqueException(e);
255         }
256     }
257 
258     /***
259      * Does nothing. We do not want to close a dataSource retrieved from Jndi,
260      * because other applications might use it as well.
261      */
262     public void close()
263     {
264         // do nothing
265     }
266 
267     /***
268      *
269      * @param ctx the context
270      * @throws NamingException
271      */
272     private void debugCtx(Context ctx) throws NamingException
273     {
274         log.debug("InitialContext -------------------------------");
275         Map env = ctx.getEnvironment();
276         Iterator qw = env.entrySet().iterator();
277         log.debug("Environment properties:" + env.size());
278         while (qw.hasNext())
279         {
280             Map.Entry entry = (Map.Entry) qw.next();
281             log.debug("    " + entry.getKey() + ": " + entry.getValue());
282         }
283         log.debug("----------------------------------------------");
284     }
285 
286     /***
287      *
288      * @param ctx
289      * @param path
290      * @param ds
291      * @throws Exception
292      */
293     private void bindDStoJndi(Context ctx, String path, Object ds)
294         throws Exception
295     {
296         debugCtx(ctx);
297 
298         // add subcontexts, if not added already
299         int start = path.indexOf(':') + 1;
300         if (start > 0)
301         {
302             path = path.substring(start);
303         }
304         StringTokenizer st = new StringTokenizer(path, "/");
305         while (st.hasMoreTokens())
306         {
307             String subctx = st.nextToken();
308             if (st.hasMoreTokens())
309             {
310                 try
311                 {
312                     ctx.createSubcontext(subctx);
313                     log.debug("Added sub context: " + subctx);
314                 }
315                 catch (NameAlreadyBoundException nabe)
316                 {
317                     // ignore
318                     log.debug("Sub context " + subctx + " already exists");
319                 }
320                 catch (NamingException ne)
321                 {
322                     log.debug("Naming exception caught "
323                                 + "when creating subcontext"
324                                 + subctx,
325                             ne);
326                     // even though there is a specific exception
327                     // for this condition, some implementations
328                     // throw the more general one.
329                     /*
330                      *                      if (ne.getMessage().indexOf("already bound") == -1 )
331                      *                      {
332                      *                      throw ne;
333                      *                      }
334                      */
335                     // ignore
336                 }
337                 ctx = (Context) ctx.lookup(subctx);
338             }
339             else
340             {
341                 // not really a subctx, it is the ds name
342                 ctx.bind(subctx, ds);
343             }
344         }
345     }
346 }