Skip to content

Commit 6a7a478

Browse files
authored
Throw exception for no result; genericize to eliminate type casts (#20)
Resolves #13
1 parent 4a4c3d2 commit 6a7a478

File tree

2 files changed

+116
-31
lines changed

2 files changed

+116
-31
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,27 @@
66

77
## What You'll Find Here
88

9+
* [ExceptionUnwrapper](#exceptionunwrapper) provides methods for extracting the contents of "wrapped" exceptions.
910
* [UncheckedThrow](#uncheckedthrow) provides a method that uses type erasure to enable you to throw checked exception as unchecked.
1011
* [DatabaseUtils](#databaseutils) provides facilities that enable you to define collections of database queries and stored procedures in an easy-to-execute format.
1112
* [Query Collections](#query-collections) are defined as Java enumerations that implement the `QueryAPI` interface
1213
* [Stored Procedure Collections](#stored-procedure-collections) are defined as Java enumerations that implement the `SProcAPI` interface
1314
* [Recommended Implementation Strategies](#recommended-implementation-strategies) to maximize usability and configurability
1415
* [Query Collection Example](#query-collection-example)
1516
* [Registering JDBC Providers](#registering-jdbc-providers) with the **ServiceLoader** facility of **DatabaseUtils**
17+
* [OSInfo](#osinfo) provides utility methods and abstractions for host operating system features.
18+
* [VolumeInfo](#volumeinfo) provides methods that parse the output of the 'mount' utility into a mapped collection of volume property records.
1619
* [PathUtils](#pathutils) provides a method to acquire the next file path in sequence for the specified base name and extension in the indicated target folder.
1720
* [Params Interface](#params-interface) defines concise methods for the creation of named parameters and parameter maps.
1821
* [JarUtils](#jarutils) provides methods related to Java JAR files:
1922
* [Assembling a Classpath String](#assembling-a-classpath-string)
2023
* [Finding a JAR File Path](#finding-a-jar-file-path)
2124
* [Extracting the `Premain-Class` Attribute](#extracting-the-premain-class-attribute)
2225

26+
## ExceptionUnwrapper
27+
28+
The **ExceptionUnwrapper** class provides methods for extracting the contents of "wrapped" exceptions.
29+
2330
## UncheckedThrow
2431

2532
The **UncheckedThrow** class uses type erasure to enable client code to throw checked exceptions as unchecked. This allows methods to throw checked exceptions without requiring clients to handle or declare them. It should be used judiciously, as this exempts client code from handling or declaring exceptions created by their own actions. The target use case for this facility is to throw exceptions that were serialized in responses from a remote system. Although the compiler won't require clients of methods using this technique to handle or declare the suppressed exception, the JavaDoc for such methods should include a `@throws` declaration for implementers who might want to handle or declare it voluntarily.
@@ -268,6 +275,14 @@ This sample provider configuration file will cause **DatabaseUtils** to load the
268275
</project>
269276
```
270277

278+
## OSInfo
279+
280+
The **OSInfo** class provides utility methods and abstractions for host operating system features.
281+
282+
## VolumeInfo
283+
284+
The **VolumeInfo** class provides methods that parse the output of the 'mount' utility into a mapped collection of volume property records.
285+
271286
## PathUtils
272287

273288
The **PathUtils** class provides a method to acquire the next file path in sequence for the specified base name and extension in the indicated target folder. If the target folder already contains at least one file that matches the specified base name and extension, the algorithm used to select the next path will always return a path whose index is one more than the highest index that currently exists. (If a single file with no index is found, its implied index is 0.)

src/main/java/com/nordstrom/common/jdbc/DatabaseUtils.java

Lines changed: 101 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -86,49 +86,75 @@ private DatabaseUtils() {
8686
}
8787

8888
/**
89-
* Execute the specified query object with supplied arguments as an 'update' operation
89+
* Execute the specified query object with supplied arguments as an 'update' operation.
9090
*
9191
* @param query query object to execute
9292
* @param queryArgs replacement values for query place-holders
9393
* @return count of records updated
9494
*/
9595
public static int update(QueryAPI query, Object... queryArgs) {
96-
Integer result = (Integer) executeQuery(null, query, queryArgs);
96+
Integer result = executeQuery(null, query, queryArgs);
9797
return (result != null) ? result.intValue() : -1;
9898
}
9999

100100
/**
101-
* Execute the specified query object with supplied arguments as a 'query' operation
101+
* Execute the specified query object with supplied arguments as a 'query' operation.
102102
*
103103
* @param query query object to execute
104104
* @param queryArgs replacement values for query place-holders
105-
* @return row 1 / column 1 as integer; -1 if no rows were returned
105+
* @return row 1 / column 1 as integer
106+
* @throws IllegalStateException if no rows were returned
106107
*/
107108
public static int getInt(QueryAPI query, Object... queryArgs) {
108-
Integer result = (Integer) executeQuery(Integer.class, query, queryArgs);
109-
return (result != null) ? result.intValue() : -1;
109+
return requireResult(Integer.class, query, queryArgs).intValue();
110110
}
111111

112112
/**
113-
* Execute the specified query object with supplied arguments as a 'query' operation
113+
* Execute the specified query object with supplied arguments as a 'query' operation.
114114
*
115115
* @param query query object to execute
116116
* @param queryArgs replacement values for query place-holders
117-
* @return row 1 / column 1 as string; {@code null} if no rows were returned
117+
* @return row 1 / column 1 as string
118+
* @throws IllegalStateException if no rows were returned
118119
*/
119120
public static String getString(QueryAPI query, Object... queryArgs) {
120-
return (String) executeQuery(String.class, query, queryArgs);
121+
return requireResult(String.class, query, queryArgs);
121122
}
122123

123124
/**
124-
* Execute the specified query object with supplied arguments as a 'query' operation
125+
* Execute the specified query object with supplied arguments as a 'query' operation.
126+
*
127+
* @param <T> desired result type
128+
* @param resultType desired result type
129+
* @param query query object to execute
130+
* @param queryArgs replacement values for query place-holders
131+
* @return a result of the indicated type
132+
* @throws IllegalStateException if no rows were returned
133+
*/
134+
private static <T> T requireResult(Class<T> resultType, QueryAPI query, Object... queryArgs) {
135+
T result = executeQuery(resultType, query, queryArgs);
136+
if (result != null) return result;
137+
138+
StringBuilder message = new StringBuilder("No result from specified query: ")
139+
.append(query.getEnum().name());
140+
141+
String[] argNames = query.getArgNames();
142+
for (int i = 0; i < argNames.length; i++) {
143+
message.append("\n").append(argNames[i]).append(": ").append(queryArgs[i]);
144+
}
145+
146+
throw new IllegalStateException(message.toString());
147+
}
148+
149+
/**
150+
* Execute the specified query object with supplied arguments as a 'query' operation.
125151
*
126152
* @param query query object to execute
127153
* @param queryArgs replacement values for query place-holders
128154
* @return {@link ResultPackage} object
129155
*/
130156
public static ResultPackage getResultPackage(QueryAPI query, Object... queryArgs) {
131-
return (ResultPackage) executeQuery(ResultPackage.class, query, queryArgs);
157+
return executeQuery(ResultPackage.class, query, queryArgs);
132158
}
133159

134160
/**
@@ -141,14 +167,15 @@ public static ResultPackage getResultPackage(QueryAPI query, Object... queryArgs
141167
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
142168
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
143169
*
170+
* @param <T> desired result type
144171
* @param resultType desired result type (see TYPES above)
145172
* @param query query object to execute
146173
* @param queryArgs replacement values for query place-holders
147174
* @return for update operations, the number of rows affected; for query operations, an object of the indicated type<br>
148175
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
149176
* when you're done with it to free up database and JDBC resources that were allocated for it.
150177
*/
151-
private static Object executeQuery(Class<?> resultType, QueryAPI query, Object... queryArgs) {
178+
private static <T> T executeQuery(Class<T> resultType, QueryAPI query, Object... queryArgs) {
152179
int expectCount = query.getArgNames().length;
153180
int actualCount = queryArgs.length;
154181

@@ -178,6 +205,7 @@ private static Object executeQuery(Class<?> resultType, QueryAPI query, Object..
178205
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
179206
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
180207
*
208+
* @param <T> desired result type
181209
* @param resultType desired result type (see TYPES above)
182210
* @param connectionStr database connection string
183211
* @param queryStr a SQL statement that may contain one or more '?' IN parameter placeholders
@@ -186,7 +214,7 @@ private static Object executeQuery(Class<?> resultType, QueryAPI query, Object..
186214
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
187215
* when you're done with it to free up database and JDBC resources that were allocated for it.
188216
*/
189-
public static Object executeQuery(Class<?> resultType, String connectionStr, String queryStr, Object... params) {
217+
public static <T> T executeQuery(Class<T> resultType, String connectionStr, String queryStr, Object... params) {
190218
try {
191219
Connection connection = getConnection(connectionStr);
192220
PreparedStatement statement = connection.prepareStatement(queryStr);
@@ -202,37 +230,72 @@ public static Object executeQuery(Class<?> resultType, String connectionStr, Str
202230
}
203231

204232
/**
205-
* Execute the specified stored procedure object with supplied parameters
233+
* Execute the specified stored procedure object with supplied parameters.
206234
*
207235
* @param sproc stored procedure object to execute
208236
* @param params an array of objects containing the input parameter values
209-
* @return row 1 / column 1 as integer; -1 if no rows were returned
237+
* @return row 1 / column 1 as integer
238+
* @throws IllegalStateException if no rows were returned
210239
*/
211240
public static int getInt(SProcAPI sproc, Object... params) {
212-
Integer result = (Integer) executeStoredProcedure(Integer.class, sproc, params);
213-
return (result != null) ? result.intValue() : -1;
241+
return requireResult(Integer.class, sproc, params).intValue();
214242
}
215243

216244
/**
217-
* Execute the specified stored procedure object with supplied parameters
245+
* Execute the specified stored procedure object with supplied parameters.
218246
*
219247
* @param sproc stored procedure object to execute
220248
* @param params an array of objects containing the input parameter values
221-
* @return row 1 / column 1 as string; {@code null} if no rows were returned
249+
* @return row 1 / column 1 as string
250+
* @throws IllegalStateException if no rows were returned
222251
*/
223252
public static String getString(SProcAPI sproc, Object... params) {
224-
return (String) executeStoredProcedure(String.class, sproc, params);
253+
return requireResult(String.class, sproc, params);
225254
}
226255

227256
/**
228-
* Execute the specified stored procedure object with supplied parameters
257+
* Execute the specified stored procedure object with supplied parameters.
258+
*
259+
* @param <T> desired result type
260+
* @param resultType desired result type
261+
* @param sproc stored procedure object to execute
262+
* @param params an array of objects containing the input parameter values
263+
* @return a result of the indicated type
264+
* @throws IllegalStateException if no rows were returned
265+
*/
266+
private static <T> T requireResult(Class<T> resultType, SProcAPI sproc, Object... params) {
267+
T result = executeStoredProcedure(resultType, sproc, params);
268+
if (result != null) return result;
269+
270+
StringBuilder message = new StringBuilder("No result from specified stored procedure: ")
271+
.append(sproc.getEnum().name());
272+
273+
int i;
274+
int argType = 0;
275+
int[] argTypes = sproc.getArgTypes();
276+
277+
for (i = 0; i < argTypes.length; i++) {
278+
argType = argTypes[i];
279+
message.append("\nparam ").append(i).append(": (type ").append(argType).append(") ").append(params[i]);
280+
}
281+
282+
// handle varargs
283+
for (; i < params.length; i++) {
284+
message.append("\nparam ").append(i).append(": (type ").append(argType).append(") ").append(params[i]);
285+
}
286+
287+
throw new IllegalStateException(message.toString());
288+
}
289+
290+
/**
291+
* Execute the specified stored procedure object with supplied parameters.
229292
*
230293
* @param sproc stored procedure object to execute
231294
* @param params an array of objects containing the input parameter values
232295
* @return {@link ResultPackage} object
233296
*/
234297
public static ResultPackage getResultPackage(SProcAPI sproc, Object... params) {
235-
return (ResultPackage) executeStoredProcedure(ResultPackage.class, sproc, params);
298+
return executeStoredProcedure(ResultPackage.class, sproc, params);
236299
}
237300

238301
/**
@@ -244,14 +307,15 @@ public static ResultPackage getResultPackage(SProcAPI sproc, Object... params) {
244307
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
245308
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
246309
*
310+
* @param <T> desired result type
247311
* @param resultType desired result type (see TYPES above)
248312
* @param sproc stored procedure object to execute
249313
* @param params an array of objects containing the input parameter values
250314
* @return an object of the indicated type<br>
251315
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
252316
* when you're done with it to free up database and JDBC resources that were allocated for it.
253317
*/
254-
public static Object executeStoredProcedure(Class<?> resultType, SProcAPI sproc, Object... params) {
318+
public static <T> T executeStoredProcedure(Class<T> resultType, SProcAPI sproc, Object... params) {
255319
Objects.requireNonNull(resultType, "[resultType] argument must be non-null");
256320

257321
String[] args = {};
@@ -315,20 +379,23 @@ public static Object executeStoredProcedure(Class<?> resultType, SProcAPI sproc,
315379
throw new IllegalArgumentException(message);
316380
}
317381

318-
Param[] parmArray = Param.array(parmsCount);
319-
320382
int i;
383+
Param[] parmArray = Param.array(parmsCount);
321384

322385
// process declared parameters
323386
for (i = 0; i < minCount; i++) {
324387
Mode mode = Mode.fromChar(args[i].charAt(0));
325388
parmArray[i] = Param.create(mode, argTypes[i], params[i]);
326389
}
327390

328-
// handle varargs parameters
329-
for (int j = i; j < parmsCount; j++) {
391+
// handle varargs
392+
if (i < parmsCount) {
393+
int argType = argTypes[i];
330394
Mode mode = Mode.fromChar(args[i].charAt(0));
331-
parmArray[j] = Param.create(mode, argTypes[i], params[j]);
395+
396+
do {
397+
parmArray[i] = Param.create(mode, argType, params[i]);
398+
} while (++i < parmsCount);
332399
}
333400

334401
return executeStoredProcedure(resultType, sproc.getConnection(), sprocName, parmArray);
@@ -343,6 +410,7 @@ public static Object executeStoredProcedure(Class<?> resultType, SProcAPI sproc,
343410
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
344411
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
345412
*
413+
* @param <T> desired result type
346414
* @param resultType desired result type (see TYPES above)
347415
* @param connectionStr database connection string
348416
* @param sprocName name of the stored procedure to be executed
@@ -351,7 +419,7 @@ public static Object executeStoredProcedure(Class<?> resultType, SProcAPI sproc,
351419
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
352420
* when you're done with it to free up database and JDBC resources that were allocated for it.
353421
*/
354-
public static Object executeStoredProcedure(Class<?> resultType, String connectionStr, String sprocName, Param... params) {
422+
public static <T> T executeStoredProcedure(Class<T> resultType, String connectionStr, String sprocName, Param... params) {
355423
Objects.requireNonNull(resultType, "[resultType] argument must be non-null");
356424

357425
StringBuilder sprocStr = new StringBuilder("{call ").append(sprocName).append("(");
@@ -391,14 +459,16 @@ public static Object executeStoredProcedure(Class<?> resultType, String connecti
391459
* <b>NOTE</b>: For all result types except {@link ResultPackage}, the specified connection and statement, as well
392460
* as the result set from executing the statement, are closed prior to returning the result.
393461
*
462+
* @param <T> desired result type
394463
* @param resultType desired result type (see TYPES above)
395464
* @param connectionStr database connection string
396465
* @param statement prepared statement to be executed (query or store procedure)
397466
* @return for update operations, the number of rows affected; for query operations, an object of the indicated type<br>
398467
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
399468
* when you're done with it to free up database and JDBC resources that were allocated for it.
400469
*/
401-
private static Object executeStatement(Class<?> resultType, Connection connection, PreparedStatement statement) {
470+
@SuppressWarnings("unchecked")
471+
private static <T> T executeStatement(Class<T> resultType, Connection connection, PreparedStatement statement) {
402472
Object result = null;
403473
boolean failed = false;
404474

@@ -467,7 +537,7 @@ private static Object executeStatement(Class<?> resultType, Connection connectio
467537
}
468538
}
469539

470-
return result;
540+
return (T) result;
471541
}
472542

473543
/**

0 commit comments

Comments
 (0)