/**
* Copyright (c) 2009-2011 VMware, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.springsource.insight.plugin.jdbc;
import static com.springsource.insight.util.StringUtil.indexOfNotIn;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import com.springsource.insight.intercept.operation.Operation;
import com.springsource.insight.intercept.operation.OperationList;
import com.springsource.insight.intercept.operation.OperationMap;
import com.springsource.insight.util.ListUtil;
import com.springsource.insight.util.StringFormatterUtils;
import com.springsource.insight.util.StringUtil;
public class JdbcOperationFinalizer {
public static final String PARAMS_VALUES = "params";
/**
* The keys in these maps should be strongly referenced in the frame stack; so they should not
* be removed until the frame leaves the station.
*/
private static final WeakKeyHashMap<Operation, Map<String, Object>> mappedParamStorage = new WeakKeyHashMap<Operation, Map<String, Object>>();
private static final WeakKeyHashMap<Operation, List<Object>> indexedParamStorage = new WeakKeyHashMap<Operation, List<Object>>();
private JdbcOperationFinalizer() {
throw new UnsupportedOperationException("No instance");
}
public static void addParam(Operation operation, String key, Object param) {
synchronized (mappedParamStorage) {
Map<String, Object> params = mappedParamStorage.get(operation);
if (params == null) {
params = new HashMap<String, Object>();
mappedParamStorage.put(operation, params);
}
params.put(key, param);
}
}
public static void addParam(Operation operation, int paramIndex, Object param) {
// JDBC indexes are 1-based, so let's adjust it to the modern world first!
int index = paramIndex - 1;
synchronized (indexedParamStorage) {
List<Object> params = indexedParamStorage.get(operation);
if (params == null) {
params = new ArrayList<Object>();
indexedParamStorage.put(operation, params);
}
// grow array if needed
while (index >= params.size()) {
params.add(null);
}
params.set(index, param);
}
}
public static Operation finalize(Operation operation) {
Map<String, Object> mappedValues;
synchronized (mappedParamStorage) {
mappedValues = mappedParamStorage.remove(operation);
}
List<Object> indexedValues;
synchronized (indexedParamStorage) {
indexedValues = indexedParamStorage.remove(operation);
}
// make sure we start with a clean slate
Serializable prev = operation.remove(PARAMS_VALUES);
if (prev != null) {
prev = null; // debug breakpoint
}
if (mappedValues != null) {
OperationMap params = operation.createMap(PARAMS_VALUES);
for (Entry<String, Object> entry : mappedValues.entrySet()) {
params.put(entry.getKey(), StringFormatterUtils.formatObjectAndTrim(entry.getValue()));
}
} else if (indexedValues != null) {
OperationList params = operation.createList(PARAMS_VALUES);
for (Object param : indexedValues) {
params.add(StringFormatterUtils.formatObjectAndTrim(param));
}
}
return operation;
}
private static final Collection<Map.Entry<String, String>> stmtsList =
Collections.unmodifiableMap(new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER) {
private static final long serialVersionUID = 1L;
{
put("SELECT", " FROM ");
put("INSERT", " INTO ");
put("DELETE", " FROM ");
put("UPDATE", "UPDATE ");
put("CREATE TABLE", " TABLE ");
put("ALTER TABLE", " TABLE ");
put("DROP TABLE", " TABLE ");
put("CREATE INDEX", " INDEX ");
put("CREATE UNIQUE INDEX", " INDEX ");
put("DROP INDEX", " INDEX ");
put("CALL", "CALL ");
}
}).entrySet();
public static String createLabel(String sql) {
if (StringUtil.isEmpty(sql)) {
return "JDBC";
}
String upperSql = sql.toUpperCase().trim();
for (Map.Entry<String, String> stmt : stmtsList) {
String kwd = stmt.getKey();
if (!upperSql.startsWith(kwd)) {
continue;
}
String argPos = stmt.getValue();
return appendArgumentValue("JDBC " + kwd, captureWordAfter(upperSql, argPos));
}
// some special extra statements
if (upperSql.startsWith("CREATE")) {
return "JDBC DML";
} else if (upperSql.startsWith("CHECKPOINT")) {
return "JDBC CHECKPOINT";
} else {
return "JDBC STATEMENT"; // could be any number of unhandled JDBC statements
}
}
private static String appendArgumentValue(String prefix, String agrValue) {
if (StringUtil.isEmpty(agrValue)) {
return prefix;
} else {
return prefix + " (" + agrValue + ")";
}
}
private static final Set<Character> WORD_DELIMS =
Collections.unmodifiableSet(ListUtil.asSet(Character.valueOf(' '), Character.valueOf('(')));
private static String captureWordAfter(String source, String delim) {
if (delim.charAt(delim.length() - 1) != ' ') {
throw new IllegalArgumentException("Last char must be a ' '");
}
int fromIdx = source.indexOf(delim);
if (fromIdx < 0) {
return null;
}
String strAfterDelim = source.substring(fromIdx + delim.length() - 1).trim();
int wordIdx = indexOfNotIn(strAfterDelim, WORD_DELIMS);
if (wordIdx < 0) {
return null;
} else if (wordIdx > 0) {
strAfterDelim = strAfterDelim.substring(wordIdx);
}
int wordEndIdx = StringUtil.indexOfIn(strAfterDelim, WORD_DELIMS);
if (wordEndIdx < 0) {
return strAfterDelim;
} else {
return strAfterDelim.substring(0, wordEndIdx);
}
}
}