1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 /*
19 * Copyright (C) The Apache Software Foundation. All rights reserved.
20 */
21
22 package com.klopotek.utils.log;
23
24 import java.sql;
25 import java.util;
26 import org.apache.log4j;
27 import org.apache.log4j.helpers;
28 import org.apache.log4j.spi;
29
30
31 /**
32 This class encapsulate the logic which is necessary to log into a table.
33 Used by JDBCAppender
34
35 <p><b>Author : </b><A HREF="mailto:t.fenner@klopotek.de">Thomas Fenner</A></p>
36
37 @since 1.0
38 */
39 public class JDBCLogger
40 {
41 //All columns of the log-table
42 private ArrayList logcols = null;
43 //Only columns which will be provided by logging
44 private String column_list = null;
45 //Number of all columns
46 private int num = 0;
47 //Status for successful execution of method configure()
48 private boolean isconfigured = false;
49 //Status for ready to do logging with method append()
50 private boolean ready = false;
51 //This message will be filled with a error-string when method ready() failes, and can be got by calling getMsg()
52 private String errormsg = "";
53
54 private Connection con = null;
55 private Statement stmt = null;
56 private ResultSet rs = null;
57 private String table = null;
58
59 //Variables for static SQL-statement logging
60 private String sql = null;
61 private String new_sql = null;
62 private String new_sql_part1 = null;
63 private String new_sql_part2 = null;
64 private static final String msg_wildcard = "@MSG@";
65 private int msg_wildcard_pos = 0;
66
67 /**
68 Writes a message into the database table.
69 Throws an exception, if an database-error occurs !
70 */
71 public void append(String _msg) throws Exception
72 {
73 if(!ready) if(!ready()) throw new Exception("JDBCLogger::append(), Not ready to append !");
74
75 if(sql != null)
76 {
77 appendSQL(_msg);
78 return;
79 }
80
81 LogColumn logcol;
82
83 rs.moveToInsertRow();
84
85 for(int i=0; i<num; i++)
86 {
87 logcol = (LogColumn)logcols.get(i);
88
89 if(logcol.logtype == LogType.MSG)
90 {
91 rs.updateObject(logcol.name, _msg);
92 }
93 else if(logcol.logtype == LogType.ID)
94 {
95 rs.updateObject(logcol.name, logcol.idhandler.getID());
96 }
97 else if(logcol.logtype == LogType.STATIC)
98 {
99 rs.updateObject(logcol.name, logcol.value);
100 }
101 else if(logcol.logtype == LogType.TIMESTAMP)
102 {
103 rs.updateObject(logcol.name, new Timestamp((new java.util.Date()).getTime()));
104 }
105 }
106
107 rs.insertRow();
108 }
109
110 /**
111 Writes a message into the database using a given sql-statement.
112 Throws an exception, if an database-error occurs !
113 */
114 public void appendSQL(String _msg) throws Exception
115 {
116 if(!ready) if(!ready()) throw new Exception("JDBCLogger::appendSQL(), Not ready to append !");
117
118 if(sql == null) throw new Exception("JDBCLogger::appendSQL(), No SQL-Statement configured !");
119
120 if(msg_wildcard_pos > 0)
121 {
122 new_sql = new_sql_part1 + _msg + new_sql_part2;
123 }
124 else new_sql = sql;
125
126 try
127 {
128 stmt.executeUpdate(new_sql);
129 }
130 catch(Exception e)
131 {
132 errormsg = new_sql;
133 throw e;
134 }
135 }
136
137
138 /**
139 Configures this class, by reading in the structure of the log-table
140 Throws an exception, if an database-error occurs !
141 */
142 public void configureTable(String _table) throws Exception
143 {
144 if(isconfigured) return;
145
146 //Fill logcols with META-informations of the table-columns
147 stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
148 rs = stmt.executeQuery("SELECT * FROM " + _table + " WHERE 1 = 2");
149
150 LogColumn logcol;
151
152 ResultSetMetaData rsmd = rs.getMetaData();
153
154 num = rsmd.getColumnCount();
155
156 logcols = new ArrayList(num);
157
158 for(int i=1; i<=num; i++)
159 {
160 logcol = new LogColumn();
161 logcol.name = rsmd.getColumnName(i).toUpperCase();
162 logcol.type = rsmd.getColumnTypeName(i);
163 logcol.nullable = (rsmd.isNullable(i) == rsmd.columnNullable);
164 logcol.isWritable = rsmd.isWritable(i);
165 if(!logcol.isWritable) logcol.ignore = true;
166 logcols.add(logcol);
167 }
168
169 table = _table;
170
171 isconfigured = true;
172 }
173
174 /**
175 Configures this class, by storing and parsing the given sql-statement.
176 Throws an exception, if somethings wrong !
177 */
178 public void configureSQL(String _sql) throws Exception
179 {
180 if(isconfigured) return;
181
182 if(!isConnected()) throw new Exception("JDBCLogger::configureSQL(), Not connected to database !");
183
184 if(_sql == null || _sql.trim().equals("")) throw new Exception("JDBCLogger::configureSQL(), Invalid SQL-Statement !");
185
186 sql = _sql.trim();
187
188 stmt = con.createStatement();
189
190 msg_wildcard_pos = sql.indexOf(msg_wildcard);
191
192 if(msg_wildcard_pos > 0)
193 {
194 new_sql_part1 = sql.substring(0, msg_wildcard_pos-1) + "'";
195 //between the message...
196 new_sql_part2 = "'" + sql.substring(msg_wildcard_pos+msg_wildcard.length());
197 }
198
199 isconfigured = true;
200 }
201
202 /**
203 Sets a connection. Throws an exception, if the connection is not open !
204 */
205 public void setConnection(Connection _con) throws Exception
206 {
207 con = _con;
208
209 if(!isConnected()) throw new Exception("JDBCLogger::setConnection(), Given connection isnt connected to database !");
210 }
211
212
213 /**
214 Sets a columns logtype (LogTypes) and value, which depends on that logtype.
215 Throws an exception, if the given arguments arent correct !
216 */
217 public void setLogType(String _name, int _logtype, Object _value) throws Exception
218 {
219 if(!isconfigured) throw new Exception("JDBCLogger::setLogType(), Not configured !");
220
221 //setLogType() makes only sense for further configuration of configureTable()
222 if(sql != null) return;
223
224 _name = _name.toUpperCase();
225
226 if(_name == null || !(_name.trim().length() > 0)) throw new Exception("JDBCLogger::setLogType(), Missing argument name !");
227 if(!LogType.isLogType(_logtype)) throw new Exception("JDBCLogger::setLogType(), Invalid logtype '" + _logtype + "' !");
228 if((_logtype != LogType.MSG && _logtype != LogType.EMPTY) && _value == null) throw new Exception("JDBCLogger::setLogType(), Missing argument value !");
229
230 LogColumn logcol;
231
232 for(int i=0; i<num; i++)
233 {
234 logcol = (LogColumn)logcols.get(i);
235
236 if(logcol.name.equals(_name))
237 {
238 if(!logcol.isWritable) throw new Exception("JDBCLogger::setLogType(), Column " + _name + " is not writeable !");
239
240 //Column gets the message
241 if(_logtype == LogType.MSG)
242 {
243 logcol.logtype = _logtype;
244 return;
245 }
246 //Column will be provided by JDBCIDHandler::getID()
247 else if(_logtype == LogType.ID)
248 {
249 logcol.logtype = _logtype;
250
251 try
252 {
253 //Try to cast directly Object to JDBCIDHandler
254 logcol.idhandler = (JDBCIDHandler)_value;
255 }
256 catch(Exception e)
257 {
258 try
259 {
260 //Assuming _value is of class string which contains the classname of a JDBCIDHandler
261 logcol.idhandler = (JDBCIDHandler)(Class.forName((String)_value).newInstance());
262 }
263 catch(Exception e2)
264 {
265 throw new Exception("JDBCLogger::setLogType(), Cannot cast value of class " + _value.getClass() + " to class JDBCIDHandler !");
266 }
267 }
268
269 return;
270 }
271
272 //Column will be statically defined with Object _value
273 else if(_logtype == LogType.STATIC)
274 {
275 logcol.logtype = _logtype;
276 logcol.value = _value;
277 return;
278 }
279
280 //Column will be provided with a actually timestamp
281 else if(_logtype == LogType.TIMESTAMP)
282 {
283 logcol.logtype = _logtype;
284 return;
285 }
286
287 //Column will be fully ignored during process.
288 //If this column is not nullable, the column has to be filled by a database trigger,
289 //else a database error occurs !
290 //Columns which are not nullable, but should be not filled, must be explicit assigned with LogType.EMPTY,
291 //else a value is required !
292 else if(_logtype == LogType.EMPTY)
293 {
294 logcol.logtype = _logtype;
295 logcol.ignore = true;
296 return;
297 }
298 }
299 }
300 }
301
302
303 /**
304 Return true, if this class is ready to append(), else false.
305 When not ready, a reason-String is stored in the instance-variable msg.
306 */
307 public boolean ready()
308 {
309 if(ready) return true;
310
311 if(!isconfigured){ errormsg = "Not ready to append ! Call configure() first !"; return false;}
312
313 //No need to doing the whole rest...
314 if(sql != null)
315 {
316 ready = true;
317 return true;
318 }
319
320 boolean msgcol_defined = false;
321
322 LogColumn logcol;
323
324 for(int i=0; i<num; i++)
325 {
326 logcol = (LogColumn)logcols.get(i);
327
328 if(logcol.ignore || !logcol.isWritable) continue;
329 if(!logcol.nullable && logcol.logtype == LogType.EMPTY)
330 {
331 errormsg = "Not ready to append ! Column " + logcol.name + " is not nullable, and must be specified by setLogType() !";
332 return false;
333 }
334 if(logcol.logtype == LogType.ID && logcol.idhandler == null)
335 {
336 errormsg = "Not ready to append ! Column " + logcol.name + " is specified as an ID-column, and a JDBCIDHandler has to be set !";
337 return false;
338 }
339 else if(logcol.logtype == LogType.STATIC && logcol.value == null)
340 {
341 errormsg = "Not ready to append ! Column " + logcol.name + " is specified as a static field, and a value has to be set !";
342 return false;
343 }
344 else if(logcol.logtype == LogType.MSG) msgcol_defined = true;
345 }
346
347 if(!msgcol_defined) return false;
348
349 //create the column_list
350 for(int i=0; i<num; i++)
351 {
352 logcol = (LogColumn)logcols.get(i);
353
354 if(logcol.ignore || !logcol.isWritable) continue;
355
356 if(logcol.logtype != LogType.EMPTY)
357 {
358 if(column_list == null)
359 {
360 column_list = logcol.name;
361 }
362 else column_list += ", " + logcol.name;
363 }
364 }
365
366 try
367 {
368 rs = stmt.executeQuery("SELECT " + column_list + " FROM " + table + " WHERE 1 = 2");
369 }
370 catch(Exception e)
371 {
372 errormsg = "Not ready to append ! Cannot select columns '" + column_list + "' of table " + table + " !";
373 return false;
374 }
375
376 ready = true;
377
378 return true;
379 }
380
381 /**
382 Return true, if this class is configured, else false.
383 */
384 public boolean isConfigured(){ return isconfigured;}
385
386 /**
387 Return true, if this connection is open, else false.
388 */
389 public boolean isConnected()
390 {
391 try
392 {
393 return (con != null && !con.isClosed());
394 }
395 catch(Exception e){return false;}
396 }
397
398 /**
399 Return the internal error message stored in instance variable msg.
400 */
401 public String getErrorMsg(){String r = new String(errormsg); errormsg = null; return r;}
402 }
403
404
405 /**
406 This class encapsulate all by class JDBCLogger needed data around a column
407 */
408 class LogColumn
409 {
410 //column name
411 String name = null;
412 //column type
413 String type = null;
414 //not nullability means that this column is mandatory
415 boolean nullable = false;
416 //isWritable means that the column can be updated, else column is only readable
417 boolean isWritable = false;
418 //if ignore is true, this column will be ignored by building sql-statements.
419 boolean ignore = false;
420
421 //Must be filled for not nullable columns ! In other case it is optional.
422 int logtype = LogType.EMPTY;
423 Object value = null; //Generic storage for typewrapper-classes Long, String, etc...
424 JDBCIDHandler idhandler = null;
425 }
426
427
428 /**
429 This class contains all constants which are necessary to define a columns log-type.
430 */
431 class LogType
432 {
433 //A column of this type will receive the message.
434 public static final int MSG = 1;
435
436 //A column of this type will be a unique identifier of the logged row.
437 public static final int ID = 2;
438
439 //A column of this type will contain a static, one-time-defined value.
440 public static final int STATIC = 3;
441
442 //A column of this type will be filled with an actual timestamp depending by the time the logging will be done.
443 public static final int TIMESTAMP = 4;
444
445 //A column of this type will contain no value and will not be included in logging insert-statement.
446 //This could be a column which will be filled not by creation but otherwhere...
447 public static final int EMPTY = 5;
448
449
450 public static boolean isLogType(int _lt)
451 {
452 if(_lt == MSG || _lt == STATIC || _lt == ID || _lt == TIMESTAMP || _lt == EMPTY) return true;
453
454 return false;
455 }
456
457 public static int parseLogType(String _lt)
458 {
459 if(_lt.equals("MSG")) return MSG;
460 if(_lt.equals("ID")) return ID;
461 if(_lt.equals("STATIC")) return STATIC;
462 if(_lt.equals("TIMESTAMP")) return TIMESTAMP;
463 if(_lt.equals("EMPTY")) return EMPTY;
464
465 return -1;
466 }
467 }
468
469
470
471
472