1 package org.apache.torque.dsfactory;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
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
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
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
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
327
328
329
330
331
332
333
334
335
336 }
337 ctx = (Context) ctx.lookup(subctx);
338 }
339 else
340 {
341
342 ctx.bind(subctx, ds);
343 }
344 }
345 }
346 }