View Javadoc
1 /* ====================================================================
2 * License:
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * 3. The end-user documentation included with the redistribution,
17 * if any, must include the following acknowledgment:
18 * "This product includes software developed by
19 * Robert Half International (http://www.rhi.com/)."
20 * Alternately, this acknowledgment may appear in the software itself,
21 * if and wherever such third-party acknowledgments normally appear.
22 *
23 * 4. The names "Parc", "RHI", and "Robert Half International" must
24 * not be used to endorse or promote products derived from this
25 * software without prior written permission. For written
26 * permission, please contact pete.mckinstry@rhi.com.
27 *
28 * 5. Products derived from this software may not be called "PARC",
29 * nor may "PARC" appear in their name, without prior written
30 * permission of Robert Half International.
31 *
32 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
33 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
35 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
36 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
38 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
39 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
40 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
41 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
42 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 * SUCH DAMAGE.
44 * ====================================================================
45 *
46 */
47 package com.rhi.architecture.batch;
48
49
50 // rhi classes
51 import com.rhi.architecture.resource.InitializationException;
52
53 import java.sql.*;
54
55 import java.text.MessageFormat;
56
57 // java classes
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collections;
61 import java.util.Iterator;
62 import java.util.List;
63 import java.util.Properties;
64
65
66 /***
67 * Loads configuration information from a database table and adds it to the property information.
68 *
69 * @author <a href="mailto:josh.tolle@rhi.com">Josh Tolle</a>
70 *
71 * @copyright 2002, Robert Half Int'l., Inc. All rights reserved.
72 * @since 1.0
73 */
74 public class HierarchicalDBConfigMixin
75 implements ConfigMixin {
76 //~ Static fields/initializers -----------------------------------------------------------------
77
78 /***
79 * The name of the property that defines SQL query to use when retrieving properties from the
80 * database.
81 */
82 public static final String SQL_QUERY_PROPERTY_NAME = "propsSqlQuery";
83
84 /***
85 * The name of the property that defines the password for the database from which properties
86 * are retrieved.
87 */
88 public static final String DB_PASSWORD_PROPERTY_NAME = "password";
89
90 /***
91 * The name of the property that defines the user for the database from which properties are
92 * retrieved.
93 */
94 public static final String DB_USER_PROPERTY_NAME = "username";
95
96 /***
97 * The name of the property that defines the URL to the database from which properties are
98 * retrieved.
99 */
100 public static final String DB_URL_PROPERTY_NAME = "db_url";
101
102 /***
103 * The name of the property that defines the driver to use when connecting to the database from
104 * which properties are retrieved.
105 */
106 public static final String DB_DRIVER_PROPERTY_NAME = "driver";
107 private static final String MISSING_PROPERTY_VALUE = "missing";
108
109 /***
110 * The name of the property that defines the name of the application that should be used to
111 * load baseline properties.
112 */
113 public static final String PARENT_PROPERTY_NAME = "parent.application";
114
115 /***
116 * The name of the property that defines the schema in the database in which the properties
117 * table resides. This is used by a call to {@link java.text.MessageFormat MessageFormat} to
118 * replace the <code>{0}</code> in the SQL statement.
119 */
120 public static final String SCHEMA_PROPERTY_NAME = "HierarchicalDBConfigMixin.schema";
121
122 /***
123 * The default column names for retrieving properties from the database.
124 */
125 public static final String[] DEFAULT_COLUMNS = { "name", "value" };
126
127 /***
128 * The SQL statement that is used by default is the {@link #SQL_QUERY_PROPERTY_NAME
129 * SQL_QUERY_PROPERTY_NAME} is not defined. The SQL statement, be it this default one or one
130 * passed in viat the SQL_QUERY_PROPERTY_NAME property, needs to have a <code>{0}</code> prior
131 * to any table names for formatting; if there is a {@link #SCHEMA_PROPERTY_NAME
132 * SCHEMA_PROPERTY_NAME} propery set, this will be replaced with the value therein, otherwise
133 * it is discarded.
134 */
135 public static final String DEFAULT_SQL =
136 "select name, value from {0}rh_ia_int_prog_config where prog_name = ? and environment = ?";
137
138 /***
139 * Separator between heirerarchical properties.
140 */
141 public static final String PROP_SEPARATOR = ".";
142
143 /***
144 * The base value for the second bindable value of the SQL statement
145 */
146 public static final String BASE_ENV = "common";
147
148 //~ Instance fields ----------------------------------------------------------------------------
149
150 private String mixinName = this.getClass().getName();
151 private String sql;
152
153 //~ Methods ------------------------------------------------------------------------------------
154
155 /***
156 * Retrieve property values and append them to the provided default application properties.
157 *
158 * <p>
159 * This method also has a recursive ability to load parent properties. It does this by looking
160 * for a property ({@link #PARENT_PROPERTY_NAME PARENT_PROPERTY_NAME}) to be set at the level
161 * at which it is looking; it will do this a far back as there are parent property values set.
162 * The recursion is done by replacing the first bindable value with the value found in {@link
163 * #PARENT_PROPERTY_NAME PARENT_PROPERTY_NAME} each time such a property is found.
164 * </p>
165 *
166 * <p>
167 * If there is no {@link #SQL_QUERY_PROPERTY_NAME SQL_QUERY_PROPERTY_NAME} property found, the
168 * default properties SQL statement is set to {@link #DEFAULT_SQL DEFAULT_SQL}. If a SQL
169 * statement is set, then the names of the columns to get from the result set as the
170 * properties "DatabaseConfigMixin.col0" and quotDatabaseConfigMixin.col1" (if
171 * these are not found, the values default to {@link #DEFAULT_COLUMNS DEFAULT_COLUMNS}), and
172 * the bindable values in the properties "DatabaseConfigMixin.val0" (used as the
173 * heirerarchical key) through "DatabaseConfigMixin.val<i>N</i>". Also, the value in
174 * the {@link #SCHEMA_PROPERTY_NAME SCHEMA_PROPERTY_NAME}, if it exists, will replace any
175 * instances of <code>{0}</code> in any SQL statement passed.
176 * </p>
177 *
178 * <p>
179 * The properties in {@link #DB_DRIVER_PROPERTY_NAME DB_DRIVER_PROPERTY_NAME}, {@link
180 * #DB_PASSWORD_PROPERTY_NAME DB_PASSWORD_PROPERTY_NAME}, {@link #DB_USER_PROPERTY_NAME
181 * DB_USER_PROPERTY_NAME}, and {@link #DB_URL_PROPERTY_NAME DB_URL_PROPERTY_NAME} are all
182 * required for this method to work.
183 * </p>
184 *
185 * @param props - the properties used to access the database
186 *
187 * @throws InitializationException if there is any problem loading properties
188 *
189 * @since 1.1
190 */
191 public void loadConfiguration(Properties props)
192 throws InitializationException {
193 mixinName = this.getClass().getName();
194 mixinName = mixinName.substring(mixinName.lastIndexOf(PROP_SEPARATOR) + 1);
195
196 Connection conn = null;
197 String schema = props.getProperty(SCHEMA_PROPERTY_NAME, "");
198
199 if (schema.length() > 0) {
200 schema = schema + ".";
201 }
202
203 Object[] querySchema = { schema };
204 this.sql =
205 MessageFormat.format(props.getProperty(SQL_QUERY_PROPERTY_NAME, DEFAULT_SQL),
206 querySchema);
207
208 ArrayList colNames = new ArrayList();
209 ArrayList bindVals = new ArrayList();
210
211 if (this.sql.equals(DEFAULT_SQL)) {
212 Properties cols = getDbColumns(props);
213
214 for (int i = 0; true; i++) {
215 String colName = "col" + i;
216 String col = cols.getProperty(colName, MISSING_PROPERTY_VALUE);
217
218 if (col.equals(MISSING_PROPERTY_VALUE)) {
219 break;
220 }
221 else {
222 colNames.add(col);
223 }
224 }
225
226 if (colNames.isEmpty()) {
227 colNames.addAll(Arrays.asList(DEFAULT_COLUMNS));
228 }
229 }
230 else {
231 colNames.addAll(Arrays.asList(DEFAULT_COLUMNS));
232 }
233
234 Properties vals = getDbValues(props);
235
236 for (int i = 0; true; i++) {
237 String valName = "val" + i;
238 String val = vals.getProperty(valName, MISSING_PROPERTY_VALUE);
239
240 if (val.equals(MISSING_PROPERTY_VALUE)) {
241 break;
242 }
243 else {
244 bindVals.add(val);
245 }
246 }
247
248 if (bindVals.isEmpty()) {
249 throw new InitializationException("binding values missing from configuration");
250 }
251
252 // Since nothing is loaded, i can't use the connection pool.
253 // Therefore make the connection explicitly.
254 String driverName = props.getProperty(DB_DRIVER_PROPERTY_NAME, MISSING_PROPERTY_VALUE);
255
256 if (driverName.equals(MISSING_PROPERTY_VALUE)) {
257 throw new InitializationException("driver missing from configuration");
258 }
259
260 String url = props.getProperty(DB_URL_PROPERTY_NAME, MISSING_PROPERTY_VALUE);
261
262 if (url.equals(MISSING_PROPERTY_VALUE)) {
263 throw new InitializationException("db_url missing from configuration");
264 }
265
266 String username = props.getProperty(DB_USER_PROPERTY_NAME, MISSING_PROPERTY_VALUE);
267
268 if (username.equals(MISSING_PROPERTY_VALUE)) {
269 throw new InitializationException("username missing from configuration");
270 }
271
272 String password = props.getProperty(DB_PASSWORD_PROPERTY_NAME, MISSING_PROPERTY_VALUE);
273
274 if (password.equals(MISSING_PROPERTY_VALUE)) {
275 throw new InitializationException("password missing from configuration");
276 }
277
278 try {
279 // register the driver.
280 Class driverClass = Class.forName(driverName);
281 conn = DriverManager.getConnection(url, username, password);
282
283 Properties recursedProperties = recurseProperties(conn, colNames, bindVals);
284 props.putAll(recursedProperties);
285 }
286 catch (SQLException e) {
287 throw new InitializationException("sql exception loading bootstrap info. e = " + e);
288 }
289 catch (ClassNotFoundException cnfe) {
290 throw new InitializationException("ClassNotFoundException = " + cnfe);
291 }
292 finally {
293 if (conn != null) {
294 try {
295 conn.close();
296 }
297 catch (SQLException e) {
298 // ignore
299 }
300 }
301 }
302 }
303
304 private Properties getDbColumns(Properties props) {
305 Properties colProps = new Properties();
306
307 for (Iterator propsIter = Collections.list(props.propertyNames()).iterator();
308 propsIter.hasNext();) {
309 String prop = (String) propsIter.next();
310
311 if (prop.startsWith(mixinName + PROP_SEPARATOR + "col")) {
312 colProps.setProperty(prop.substring((prop.indexOf(PROP_SEPARATOR) + 1)),
313 props.getProperty(prop));
314 }
315 }
316
317 return colProps;
318 }
319
320 private Properties getDbValues(Properties props) {
321 Properties valProps = new Properties();
322
323 for (Iterator propsIter = Collections.list(props.propertyNames()).iterator();
324 propsIter.hasNext();) {
325 String prop = (String) propsIter.next();
326
327 if (prop.startsWith(mixinName + PROP_SEPARATOR + "val")) {
328 valProps.setProperty(prop.substring((prop.indexOf(PROP_SEPARATOR) + 1)),
329 props.getProperty(prop));
330 }
331 }
332
333 return valProps;
334 }
335
336 private Properties loadProperties(Connection conn, List cols, List vals)
337 throws InitializationException {
338 Properties props = new Properties();
339 PreparedStatement stmt = null;
340 ResultSet rs = null;
341
342 try {
343 stmt = conn.prepareStatement(this.sql);
344
345 for (int i = 0, n = vals.size(); i < n; i++) {
346 stmt.setObject((i + 1), vals.get(i));
347 }
348
349 rs = stmt.executeQuery();
350
351 // add any properties to the list.
352 while (rs.next()) {
353 String name = rs.getString((String) cols.get(0));
354 String value = rs.getString((String) cols.get(1));
355 props.setProperty(name, value);
356 }
357
358 return props;
359 }
360 catch (SQLException e) {
361 throw new InitializationException("sql exception loading bootstrap info. e = " + e);
362 }
363 finally {
364 if (rs != null) {
365 try {
366 rs.close();
367 }
368 catch (SQLException e) {
369 // ignore
370 }
371 }
372
373 if (stmt != null) {
374 try {
375 stmt.close();
376 }
377 catch (SQLException e) {
378 // ignore
379 }
380 }
381 }
382
383 // finally block
384 }
385
386 private Properties recurseProperties(Connection conn, List colNames, List bindVals)
387 throws InitializationException {
388 // get properties from database
389 Properties resultProps = new Properties();
390 Properties interfaceProps = new Properties();
391 Properties parentProps = new Properties();
392 List baseBinding = new ArrayList(bindVals);
393 baseBinding.set(1, BASE_ENV);
394 interfaceProps.putAll(loadProperties(conn, colNames, baseBinding));
395 interfaceProps.putAll(loadProperties(conn, colNames, bindVals));
396
397 String parentProp =
398 interfaceProps.getProperty(PARENT_PROPERTY_NAME, MISSING_PROPERTY_VALUE);
399
400 if (!parentProp.equals(MISSING_PROPERTY_VALUE)) {
401 bindVals.set(0, parentProp);
402 parentProps = recurseProperties(conn, colNames, bindVals);
403 }
404
405 resultProps.putAll(parentProps);
406 resultProps.putAll(interfaceProps);
407
408 return resultProps;
409 }
410
411 // end loadProperties() method.
412 }
This page was automatically generated by Maven