1 /*
2 ~ Copyright 2006-2007 Nicolas De Loof.
3 ~
4 ~ Licensed under the Apache License, Version 2.0 (the "License");
5 ~ you may not use this file except in compliance with the License.
6 ~ You may obtain a copy of the License at
7 ~
8 ~ http://www.apache.org/licenses/LICENSE-2.0
9 ~
10 ~ Unless required by applicable law or agreed to in writing, software
11 ~ distributed under the License is distributed on an "AS IS" BASIS,
12 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ~ See the License for the specific language governing permissions and
14 ~ limitations under the License.
15 */
16 package org.jmonit.support.jdbc;
17
18 import java.sql.SQLException;
19 import java.util.StringTokenizer;
20
21 import org.jmonit.Monitor;
22 import org.jmonit.Monitoring;
23
24 /**
25 * @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
26 */
27 public class JdbcMonitor
28 {
29 /**
30 * The root jdbc monitor
31 */
32 private Monitor monitor;
33
34 /**
35 * Constructor
36 *
37 * @param monitor The monitor used as root for JDBC monitoring
38 */
39 public JdbcMonitor( Monitor monitor )
40 {
41 super();
42 this.monitor = monitor.tag( "jdbc" );
43 }
44
45 public Monitor getConnectionMonitor()
46 {
47 return this.monitor;
48 }
49
50 /**
51 * @param sql SQL request
52 * @return a monitor for this statement
53 */
54 public Monitor getStatementMonitor( String sql )
55 {
56 return getSubMonitor( generalizeSql( sql ), "statement" );
57 }
58
59 /**
60 * @param sql SQL request
61 * @return a monitor for this preapredStatement
62 */
63 public Monitor getPreparedStatementMonitor( String sql )
64 {
65 return getSubMonitor( sql, "preparedStatement" );
66 }
67
68 /**
69 * @param sql SQL request
70 * @return a monitor for this callableStatement
71 */
72 public Monitor getCallableStatementMonitor( String sql )
73 {
74 return getSubMonitor( sql, "callableStatement" );
75 }
76
77 public void monitorSQLException( SQLException sqle )
78 {
79 Monitoring.add( monitor.getName() + ".SQLException" + sqle.getErrorCode(), 1 );
80 }
81
82 private Monitor getSubMonitor( String sql, String type )
83 {
84 StringBuffer name = new StringBuffer( monitor.getName() );
85 name.append( "." ).append( type ).append( "~" ).append( sql );
86
87 return Monitoring.getMonitor( name.toString() ).tag( "jdbc" ).tag( type );
88 }
89
90 /**
91 * Convert a SQL query to a "generic" query by removing any hard coded
92 * value. This makes the SQL look like a PreparedStatement.
93 *
94 * @param sql SQL query
95 * @return generalized SQL query
96 */
97 protected String generalizeSql( String sql )
98 {
99 StringBuffer stb = new StringBuffer();
100 StringTokenizer tokenizer = new StringTokenizer( sql, " \t\r\n,)=", true );
101 boolean ws = false;
102 while ( tokenizer.hasMoreTokens() )
103 {
104 String token = tokenizer.nextToken();
105 if ( token.length() == 1 && Character.isWhitespace( token.charAt( 0 ) ) )
106 {
107 if ( !ws )
108 {
109 stb.append( " " );
110 }
111 ws = true;
112 continue;
113 }
114 ws = false;
115 char first = token.charAt( 0 );
116 char last = token.charAt( token.length() - 1 );
117 if ( first == last && ( first == '\'' || first == '"' ) )
118 {
119 // Quoted value
120 token = "?";
121 }
122 else
123 {
124 boolean numeric = true;
125 char[] chars = token.toCharArray();
126 boolean dot = false;
127 for ( int i = 0; i < chars.length; i++ )
128 {
129 if ( !Character.isDigit( chars[i] ) && !dot && chars[i] != '.' )
130 {
131 numeric = false;
132 break;
133 }
134 if ( chars[i] == '.' )
135 {
136 dot = true;
137 }
138 }
139 if ( numeric )
140 {
141 // Hard coded Numeric
142 token = "?";
143 }
144 }
145 stb.append( token );
146 }
147 return stb.toString().toLowerCase();
148 }
149 }