1 package net.sf.mavenhist.persistency;
2
3 import java.math.BigDecimal;
4 import java.sql.Connection;
5 import java.sql.DriverManager;
6 import java.sql.PreparedStatement;
7 import java.sql.ResultSet;
8 import java.sql.SQLException;
9 import java.sql.Timestamp;
10
11 import net.sf.mavenhist.util.ParamCheckUtil;
12
13 import org.apache.commons.lang.builder.ToStringBuilder;
14
15 /**
16 * Supports an easy implementation of a DB-persistency Layer. It supports connection handling and
17 * execution of queries with exception handling.
18 */
19 public abstract class AbstractDatabasePersistency implements IPersistency, IConfigurablePersistency {
20
21 private String dbUser;
22 private String dbPassword;
23 private String dbURL;
24
25 /**
26 * Creates a new db persistency object.
27 * @param dbUser the username for the db.
28 * @param dbPassword the password for the db.
29 * @param dbURL the server URL of the db.
30 */
31 public AbstractDatabasePersistency(String dbUser, String dbPassword,
32 String dbURL) {
33 this.dbUser = dbUser;
34 this.dbPassword = dbPassword;
35 this.dbURL = dbURL;
36 }
37
38 /**
39 * Gets a db connection, must be released by calling
40 * {@link #closeConnection(Connection)}.
41 *
42 * @return db connection
43 * @throws PersistencyException in case of error
44 */
45 protected Connection getConnection() throws PersistencyException {
46
47 Connection conn;
48 try {
49 Class.forName(getDbDriverClass());
50 conn = DriverManager.getConnection(getURL(), getUser(), getPassword());
51 conn.setAutoCommit(false);
52 } catch (ClassNotFoundException e) {
53 throw new PersistencyException("Could not load db driver.", e);
54 } catch (SQLException e) {
55 throw new PersistencyException("Could not get db connection.", e);
56 }
57
58 return conn;
59 }
60
61 /**
62 * Commits on connection and rethrows eventually generated <code>Exceptions</code>.
63 *
64 * @param conn to commit on.
65 * @throws PersistencyException thrown when commit fails.
66 */
67 protected void commit(Connection conn) throws PersistencyException {
68 try {
69 conn.commit();
70 } catch (SQLException e) {
71 throw new PersistencyException("Could not commit.", e);
72 }
73 }
74
75 /**
76 * Closes a db connection opened by {@link #getConnection()}.
77 *
78 * @param conn to be closed.
79 * @throws PersistencyException in case of error
80 */
81 protected void closeConnection(Connection conn) throws PersistencyException {
82 try {
83 if (conn != null) {
84 conn.close();
85 }
86 } catch (SQLException e) {
87 throw new PersistencyException("Could not close connection.", e);
88 }
89 }
90
91 /**
92 * Sets prepared params into a prepared Statement.
93 *
94 * @param preparedStatement to be set params on.
95 * @param args param array.
96 * @throws PersistencyException the arguments didn't match the prepared statement.
97 * @throws SQLException thrown when the parameters do not match.
98 */
99 private void setPreparedStatementParams(PreparedStatement preparedStatement, Object[] args)
100 throws SQLException, PersistencyException {
101 if (args == null) {
102 throw new PersistencyException("Null as args is not allowed for a prepared statement");
103 }
104 int parameterCount = preparedStatement.getParameterMetaData().getParameterCount();
105 if (parameterCount != args.length) {
106 throw new PersistencyException("Number of arguments for the prepared statement are invalid. "
107 + "Is: " + args.length + " and should be: "
108 + parameterCount);
109 }
110 for (int i = 0; i < args.length; i++) {
111 Object arg = args[i];
112 if (arg instanceof String) {
113 preparedStatement.setString(i + 1, (String) arg);
114 } else if (arg instanceof java.util.Date) {
115
116 Timestamp sqlTimestamp = new Timestamp((((java.util.Date) arg).getTime()));
117 preparedStatement.setTimestamp(i + 1, sqlTimestamp);
118 } else if (arg instanceof Long) {
119 preparedStatement.setLong(i + 1, ((java.lang.Long) arg).longValue());
120 } else if (arg instanceof BigDecimal) {
121 preparedStatement.setBigDecimal(i + 1, (BigDecimal) arg);
122 } else {
123
124
125 preparedStatement.setObject(i + 1, arg);
126 }
127 }
128 }
129
130 /**
131 * Executes a query operation on the database.
132 *
133 * @param sql to be queried.
134 * @param args to be filled in for all '?'
135 * @param resultSetProcessor to process the result set.
136 * @return the result of the resultSetProcessor that was given to the method.
137 * @throws PersistencyException in case of error.
138 */
139 public Object executeQueryStatement(String sql, Object[] args,
140 IResultSetProcessor resultSetProcessor) throws PersistencyException {
141 return executeQueryStatement(getConnection(), sql, args, resultSetProcessor);
142 }
143
144 /**
145 * Executes a query operation on the database.
146 *
147 * @param conn to be used.
148 * @param sql to be queried.
149 * @param args to be filled in for all '?'
150 * @param resultSetProcessor to process the result set.
151 * @return the result of the resultSetProcessor that was given to the method.
152 * @throws PersistencyException in case of error.
153 */
154 protected Object executeQueryStatement(Connection conn, String sql,
155 Object[] args, IResultSetProcessor resultSetProcessor)
156 throws PersistencyException {
157
158 PreparedStatement preparedStatement = null;
159 ResultSet rs = null;
160 try {
161 preparedStatement = conn.prepareStatement(sql);
162
163
164 setPreparedStatementParams(preparedStatement, args);
165 rs = preparedStatement.executeQuery();
166 return resultSetProcessor.processResult(rs);
167 } catch (SQLException e) {
168 throw new PersistencyException("Could not execute statement: " + sql, e);
169 } catch (PersistencyException e) {
170 throw e;
171 } catch (Exception e) {
172 throw new PersistencyException(
173 "Could not process result set for statement: " + sql, e);
174 } finally {
175 closeResultSet(rs);
176 closePreparedStatement(preparedStatement);
177 }
178 }
179
180 /**
181 * Executes a create operation on the database.
182 *
183 * @param sql to be queried.
184 * @return true if the first result is a {@link ResultSet} object; false if the first result is
185 * an update count or there is no result.
186 * @throws PersistencyException in case of error.
187 */
188 public boolean executeCreateStatement(String sql) throws PersistencyException {
189 return executeCreateStatement(getConnection(), sql);
190 }
191
192 /**
193 * Executes a create operation on the database.
194 *
195 * @param conn to be used.
196 * @param sql to be queried.
197 * @return true if the first result is a {@link ResultSet} object; false if the first result is
198 * an update count or there is no result
199 * @throws PersistencyException in case of error.
200 */
201 protected boolean executeCreateStatement(Connection conn, String sql) throws PersistencyException {
202 PreparedStatement preparedStatement = null;
203 try {
204 preparedStatement = conn.prepareStatement(sql);
205 return preparedStatement.execute();
206 } catch (SQLException e) {
207 throw new PersistencyException("Could not execute statement: " + sql, e);
208 } finally {
209 closePreparedStatement(preparedStatement);
210 }
211 }
212
213 /**
214 * Closes the given resultset and writes the exception if a problem occured.
215 * @param rs to close.
216 */
217 private void closeResultSet(ResultSet rs) {
218 try {
219 if (rs != null) {
220 rs.close();
221 }
222 } catch (SQLException e) {
223 e.printStackTrace();
224 }
225 }
226
227 /**
228 * Executes an update statement (queries wont work, only
229 * insert/update calls allowed). Commits implicitely.
230 *
231 * @param sql to be executed
232 * @param args to be filled in for all '?'
233 * @return update record count
234 * @throws PersistencyException in case of error
235 */
236 public int executeUpdateStatement(String sql, Object[] args)
237 throws PersistencyException {
238 Connection conn = null;
239 try {
240 conn = getConnection();
241 int updateCount = executeUpdateStatement(conn, sql, args);
242 commit(conn);
243 return updateCount;
244 } finally {
245 closeConnection(conn);
246 }
247 }
248
249 /**
250 * Executes an update statement (queries won't work, only insert/update calls allowed).
251 *
252 * @param conn connection to be used
253 * @param sql to be executed
254 * @param args to be filled in for all '?'
255 * @return update record count
256 * @throws PersistencyException in case of error
257 */
258 protected int executeUpdateStatement(Connection conn, String sql, Object[] args)
259 throws PersistencyException {
260 PreparedStatement preparedStatement = null;
261 try {
262 preparedStatement = conn.prepareStatement(sql);
263
264 setPreparedStatementParams(preparedStatement, args);
265 return preparedStatement.executeUpdate();
266 } catch (SQLException e) {
267 throw new PersistencyException("Could not execute statement: " + sql, e);
268 } finally {
269 closePreparedStatement(preparedStatement);
270 }
271 }
272
273 /**
274 * Closes the given prepared statement and prints an error if it fails.
275 *
276 * @param preparedStatement to close.
277 */
278 private void closePreparedStatement(PreparedStatement preparedStatement) {
279 try {
280 if (preparedStatement != null) {
281 preparedStatement.close();
282 }
283 } catch (SQLException e) {
284 e.printStackTrace();
285 }
286 }
287
288 /**
289 * Stores the metrics by calling the method <code>storeMetric</code>.
290 * {@inheritDoc}
291 */
292 public void storeMetrics(Metric[] metrics) throws PersistencyException {
293 isNecessaryParamSet();
294
295 Connection conn = null;
296 try {
297 conn = getConnection();
298 for (int i = 0; i < metrics.length; i++) {
299 Metric metric = metrics[i];
300 if (metric.getMetricValue() != null) {
301 storeMetric(metrics[i], conn);
302 } else {
303 System.out.println("Skipping metric storage, no value available for metric: " + metric);
304 }
305 }
306 } finally {
307 closeConnection(conn);
308 }
309 }
310
311 /**
312 * Checks that the necessairy parameters are set in the {@link AbstractDatabasePersistency} that
313 * it can store values successful.
314 * Throws <code>IllegalStateException</code> when a parameter is not set.
315 *
316 * @return are all needed parameter set.
317 */
318 private boolean isNecessaryParamSet() {
319 StringBuffer messageBuffer = new StringBuffer();
320 boolean paramsOk = ParamCheckUtil.checkParam(messageBuffer, getDbDriverClass(), "DriverClass")
321 & ParamCheckUtil.checkParam(messageBuffer, getURL(), "Url")
322 & ParamCheckUtil.checkParam(messageBuffer, getUser(), "User");
323 if (!paramsOk) {
324 throw new IllegalStateException(messageBuffer.toString());
325 }
326 return true;
327 }
328
329 /**
330 * Stores the metric by calling the method <code>storeMetric</code>.
331 *
332 * @param metric metric to store in the DB.
333 * @throws PersistencyException if storing fails.
334 */
335 public void storeMetric(Metric metric) throws PersistencyException {
336 storeMetrics(new Metric[] {metric});
337 }
338
339 /**
340 * Hook implementation to store a metric value in a customized matter.
341 *
342 * @param metric to store in the DB.
343 * @param conn to access the database and store the metric.
344 * @throws PersistencyException thrown if something fails.
345 */
346 protected abstract void storeMetric(final Metric metric, Connection conn)
347 throws PersistencyException;
348
349 /**
350 * Gets dbPassword.
351 * @return dbPassword
352 */
353 public String getPassword() {
354 return dbPassword;
355 }
356
357 /**
358 * Sets dbPassword.
359 * @param dbPassword dbPassword to set
360 */
361 public void setPassword(String dbPassword) {
362 this.dbPassword = dbPassword;
363 }
364
365 /**
366 * Gets dbURL.
367 * @return dbURL
368 */
369 public String getURL() {
370 return dbURL;
371 }
372
373 /**
374 * Sets dbURL.
375 * @param dbURL dbURL to set
376 */
377 public void setURL(String dbURL) {
378 this.dbURL = dbURL;
379 }
380
381 /**
382 * Gets dbUser.
383 * @return dbUser
384 */
385 public String getUser() {
386 return dbUser;
387 }
388
389 /**
390 * Sets dbUser.
391 * @param dbUser dbUser to set
392 */
393 public void setUser(String dbUser) {
394 this.dbUser = dbUser;
395 }
396
397 /**
398 * Gets dbDriverName.
399 * @return DB-drivername
400 */
401 public abstract String getDbDriverClass();
402
403 /**
404 * {@inheritDoc}
405 */
406 public String toString() {
407 return ToStringBuilder.reflectionToString(this);
408 }
409 }