View Javadoc

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