001: package org.apache.torque.dsfactory;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.Hashtable;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.StringTokenizer;
026:
027: import javax.naming.Context;
028: import javax.naming.InitialContext;
029: import javax.naming.NameAlreadyBoundException;
030: import javax.naming.NamingException;
031: import javax.sql.DataSource;
032:
033: import org.apache.commons.configuration.Configuration;
034:
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037:
038: import org.apache.torque.TorqueException;
039:
040: /**
041: * A factory that looks up the DataSource from JNDI. It is also able
042: * to deploy the DataSource based on properties found in the
043: * configuration.
044: *
045: * This factory tries to avoid excessive context lookups to improve speed.
046: * The time between two lookups can be configured. The default is 0 (no cache).
047: *
048: * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
049: * @author <a href="mailto:thomas@vandahl.org">Thomas Vandahl</a>
050: * @version $Id: JndiDataSourceFactory.java 476550 2006-11-18 16:08:37Z tfischer $
051: */
052: public class JndiDataSourceFactory extends AbstractDataSourceFactory {
053: /**
054: * Key for the configuration which contains jndi properties.
055: */
056: public static final String JNDI_KEY = "jndi";
057:
058: /**
059: * Key for the configuration property which contains the jndi path.
060: */
061: public static final String PATH_KEY = "path";
062:
063: /**
064: * Key for the configuration property which contains the
065: * time between two jndi lookups.
066: */
067: public static final String TIME_BETWEEN_LOOKUPS_KEY = "ttl";
068:
069: /**
070: * Key for the configuration which contains properties for a DataSource
071: * which should be bound into jndi.
072: */
073: public static final String DATASOURCE_KEY = "datasource";
074:
075: /**
076: * Key for the configuration property which contains the class name
077: * of the datasource to be bound into jndi.
078: */
079: public static final String CLASSNAME_KEY = "classname";
080:
081: /** The log. */
082: private static Log log = LogFactory
083: .getLog(JndiDataSourceFactory.class);
084:
085: /** The path to get the resource from. */
086: private String path;
087: /** The context to get the resource from. */
088: private Context ctx;
089:
090: /** A locally cached copy of the DataSource */
091: private DataSource ds = null;
092:
093: /** Time of last actual lookup action */
094: private long lastLookup = 0;
095:
096: /** Time between two lookups */
097: private long ttl = 0; // ms
098:
099: /**
100: * @see org.apache.torque.dsfactory.DataSourceFactory#getDataSource
101: */
102: public DataSource getDataSource() throws TorqueException {
103: long time = System.currentTimeMillis();
104:
105: if (ds == null || time - lastLookup > ttl) {
106: try {
107: ds = ((DataSource) ctx.lookup(path));
108: lastLookup = time;
109: } catch (Exception e) {
110: throw new TorqueException(e);
111: }
112: }
113:
114: return ds;
115: }
116:
117: /**
118: * @see org.apache.torque.dsfactory.DataSourceFactory#initialize
119: */
120: public void initialize(Configuration configuration)
121: throws TorqueException {
122: super .initialize(configuration);
123:
124: initJNDI(configuration);
125: initDataSource(configuration);
126: }
127:
128: /**
129: * Initializes JNDI.
130: *
131: * @param configuration where to read the settings from
132: * @throws TorqueException if a property set fails
133: */
134: private void initJNDI(Configuration configuration)
135: throws TorqueException {
136: log.debug("Starting initJNDI");
137:
138: Configuration c = configuration.subset(JNDI_KEY);
139: if (c == null || c.isEmpty()) {
140: throw new TorqueException(
141: "JndiDataSourceFactory requires a jndi "
142: + "path property to lookup the DataSource in JNDI.");
143: }
144:
145: try {
146: Hashtable env = new Hashtable();
147: for (Iterator i = c.getKeys(); i.hasNext();) {
148: String key = (String) i.next();
149: if (key.equals(PATH_KEY)) {
150: path = c.getString(key);
151: if (log.isDebugEnabled()) {
152: log.debug("JNDI path: " + path);
153: }
154: } else if (key.equals(TIME_BETWEEN_LOOKUPS_KEY)) {
155: ttl = c.getLong(key, ttl);
156: if (log.isDebugEnabled()) {
157: log.debug("Time between context lookups: "
158: + ttl);
159: }
160: } else {
161: String value = c.getString(key);
162: env.put(key, value);
163: if (log.isDebugEnabled()) {
164: log.debug("Set jndi property: " + key + "="
165: + value);
166: }
167: }
168: }
169:
170: ctx = new InitialContext(env);
171: log.debug("Created new InitialContext");
172: debugCtx(ctx);
173: } catch (Exception e) {
174: log.error("", e);
175: throw new TorqueException(e);
176: }
177: }
178:
179: /**
180: * Initializes the DataSource.
181: *
182: * @param configuration where to read the settings from
183: * @throws TorqueException if a property set fails
184: */
185: private void initDataSource(Configuration configuration)
186: throws TorqueException {
187: log.debug("Starting initDataSource");
188: try {
189: Object dataSource = null;
190:
191: Configuration c = configuration.subset(DATASOURCE_KEY);
192: if (c != null) {
193: for (Iterator i = c.getKeys(); i.hasNext();) {
194: String key = (String) i.next();
195: if (key.equals(CLASSNAME_KEY)) {
196: String classname = c.getString(key);
197: if (log.isDebugEnabled()) {
198: log.debug("Datasource class: " + classname);
199: }
200:
201: Class dsClass = Class.forName(classname);
202: dataSource = dsClass.newInstance();
203: } else {
204: if (dataSource != null) {
205: if (log.isDebugEnabled()) {
206: log
207: .debug("Setting datasource property: "
208: + key);
209: }
210: setProperty(key, c, dataSource);
211: } else {
212: log
213: .error("Tried to set property "
214: + key
215: + " without Datasource definition!");
216: }
217: }
218: }
219: }
220:
221: if (dataSource != null) {
222: bindDStoJndi(ctx, path, dataSource);
223: }
224: } catch (Exception e) {
225: log.error("", e);
226: throw new TorqueException(e);
227: }
228: }
229:
230: /**
231: * Does nothing. We do not want to close a dataSource retrieved from Jndi,
232: * because other applications might use it as well.
233: */
234: public void close() {
235: // do nothing
236: }
237:
238: /**
239: *
240: * @param ctx the context
241: * @throws NamingException
242: */
243: private void debugCtx(Context ctx) throws NamingException {
244: log.debug("InitialContext -------------------------------");
245: Map env = ctx.getEnvironment();
246: Iterator qw = env.entrySet().iterator();
247: log.debug("Environment properties:" + env.size());
248: while (qw.hasNext()) {
249: Map.Entry entry = (Map.Entry) qw.next();
250: log
251: .debug(" " + entry.getKey() + ": "
252: + entry.getValue());
253: }
254: log.debug("----------------------------------------------");
255: }
256:
257: /**
258: *
259: * @param ctx
260: * @param path
261: * @param ds
262: * @throws Exception
263: */
264: private void bindDStoJndi(Context ctx, String path, Object ds)
265: throws Exception {
266: debugCtx(ctx);
267:
268: // add subcontexts, if not added already
269: int start = path.indexOf(':') + 1;
270: if (start > 0) {
271: path = path.substring(start);
272: }
273: StringTokenizer st = new StringTokenizer(path, "/");
274: while (st.hasMoreTokens()) {
275: String subctx = st.nextToken();
276: if (st.hasMoreTokens()) {
277: try {
278: ctx.createSubcontext(subctx);
279: log.debug("Added sub context: " + subctx);
280: } catch (NameAlreadyBoundException nabe) {
281: // ignore
282: log.debug("Sub context " + subctx
283: + " already exists");
284: } catch (NamingException ne) {
285: log.debug("Naming exception caught "
286: + "when creating subcontext" + subctx, ne);
287: // even though there is a specific exception
288: // for this condition, some implementations
289: // throw the more general one.
290: /*
291: * if (ne.getMessage().indexOf("already bound") == -1 )
292: * {
293: * throw ne;
294: * }
295: */
296: // ignore
297: }
298: ctx = (Context) ctx.lookup(subctx);
299: } else {
300: // not really a subctx, it is the ds name
301: ctx.bind(subctx, ds);
302: }
303: }
304: }
305: }
|