/*
* Copyright 2011-2017 the original author or authors.
*
* 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 org.glowroot.agent.plugin.jdbc;
import java.util.Collection;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Queues;
import com.google.common.hash.HashCode;
import org.glowroot.agent.plugin.jdbc.message.BindParameterList;
// used to capture and mirror the state of prepared statements since the underlying
// PreparedStatement values cannot be inspected after they have been set
class PreparedStatementMirror extends StatementMirror {
private static final int CAPTURED_BATCH_SIZE_LIMIT = 1000;
private static final int PARAMETERS_INITIAL_CAPACITY = 4;
private final String sql;
// ok for this field to be non-volatile since it is only temporary storage for a single thread
// while that thread is setting parameter values into the prepared statement and executing it
private BindParameterList parameters;
private boolean parametersShared;
// ok for this field to be non-volatile since it is only temporary storage for a single thread
// while that thread is setting parameter values into the prepared statement and executing it
private @Nullable Collection<BindParameterList> batchedParameters;
private int batchSize;
PreparedStatementMirror(String sql) {
this.sql = sql;
// TODO delay creation to optimize case when bind parameter capture is disabled
parameters = new BindParameterList(PARAMETERS_INITIAL_CAPACITY);
}
void addBatch() {
// synchronization isn't an issue here as this method is called only by the monitored thread
if (batchedParameters == null) {
batchedParameters = Queues.newConcurrentLinkedQueue();
}
if (batchSize++ < CAPTURED_BATCH_SIZE_LIMIT) {
batchedParameters.add(parameters);
parametersShared = true;
}
}
Collection<BindParameterList> getBatchedParameters() {
if (batchedParameters == null) {
return ImmutableList.of();
} else {
return batchedParameters;
}
}
@Nullable
BindParameterList getParameters() {
parametersShared = true;
return parameters;
}
String getSql() {
return sql;
}
int getBatchSize() {
return batchSize;
}
// remember parameterIndex starts at 1 not 0
void setParameterValue(int parameterIndex, @Nullable Object object) {
if (parametersShared) {
// separate method for less common path to not impact inlining budget of fast(er) path
copyParameters();
}
parameters.set(parameterIndex - 1, object);
}
private void copyParameters() {
parameters = BindParameterList.copyOf(parameters);
parametersShared = false;
}
void clearParameters() {
if (parametersShared) {
parameters = new BindParameterList(parameters.size());
parametersShared = false;
} else {
parameters.clear();
}
}
@Override
public void clearBatch() {
if (parametersShared) {
parameters = new BindParameterList(parameters.size());
parametersShared = false;
} else {
parameters.clear();
}
batchedParameters = null;
batchSize = 0;
}
static class ByteArrayParameterValue {
private final int length;
private final byte /*@Nullable*/[] bytes;
ByteArrayParameterValue(byte[] bytes, boolean displayAsHex) {
length = bytes.length;
// only retain bytes if needed for displaying as hex
this.bytes = displayAsHex ? bytes : null;
}
@Override
public String toString() {
if (bytes != null) {
return "0x" + HashCode.fromBytes(bytes).toString();
} else {
return "{" + length + " bytes}";
}
}
}
static class StreamingParameterValue {
private final Class<?> clazz;
StreamingParameterValue(Class<?> clazz) {
this.clazz = clazz;
}
@Override
public String toString() {
return "{stream:" + clazz.getSimpleName() + "}";
}
}
}