Skip to content

Commit

Permalink
Binary data representation can be customized according to database di…
Browse files Browse the repository at this point in the history
…alect (p6spy#502)

* Binary data representation can be customized according to database dialect

* BinaryFormat#toString wraps its return value in quotes if needed

Co-authored-by: Jonne Jyrylä <jonne.jyryla@live.com>
  • Loading branch information
kekbur and kekbur committed Jul 24, 2020
1 parent c4392d2 commit fc1d70e
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 35 deletions.
6 changes: 6 additions & 0 deletions docs/configandusage.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ in section: [Configuration and Usage](#configuration-and-usage)):
# format that is used for logging booleans, possible values: boolean, numeric
# (default is boolean)
#databaseDialectBooleanFormat=boolean

# Specifies the format for logging binary data. Not applicable if excludebinary is true.
# (default is com.p6spy.engine.logging.format.HexEncodedBinaryFormat)
#databaseDialectBinaryFormat=com.p6spy.engine.logging.format.PostgreSQLBinaryFormat
#databaseDialectBinaryFormat=com.p6spy.engine.logging.format.MySQLBinaryFormat
#databaseDialectBinaryFormat=com.p6spy.engine.logging.format.HexEncodedBinaryFormat

# whether to expose options via JMX or not
# (default is true)
Expand Down
6 changes: 6 additions & 0 deletions src/main/assembly/individualFiles/spy.properties
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@
# (default is boolean)
# databaseDialectBooleanFormat=boolean

# Specifies the format for logging binary data. Not applicable if excludebinary is true.
# (default is com.p6spy.engine.logging.format.HexEncodedBinaryFormat)
#databaseDialectBinaryFormat=com.p6spy.engine.logging.format.PostgreSQLBinaryFormat
#databaseDialectBinaryFormat=com.p6spy.engine.logging.format.MySQLBinaryFormat
#databaseDialectBinaryFormat=com.p6spy.engine.logging.format.HexEncodedBinaryFormat

# whether to expose options via JMX or not
# (default is true)
#jmx=true
Expand Down
56 changes: 21 additions & 35 deletions src/main/java/com/p6spy/engine/common/Value.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.p6spy.engine.logging.P6LogLoadableOptions;
import com.p6spy.engine.logging.P6LogOptions;
import com.p6spy.engine.logging.format.BinaryFormat;
import com.p6spy.engine.spy.P6SpyOptions;

import java.sql.Timestamp;
Expand All @@ -35,9 +36,6 @@
*/
public class Value {

private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
'F' };

/**
* Value itself.
*/
Expand Down Expand Up @@ -70,8 +68,8 @@ public String toString() {
* <ul>
* <li>{@link java.util.Date} values it in a way configured via configuration
* property: {@code dateformat},</li>
* <li>{@code byte[]} values are converted to {@link String} Hexadecimal
* representation, unless configuration property {@code exclidebinary=true} is
* <li>{@code byte[]} values are converted to {@link String} representation using the configured
* database dialect {@link BinaryFormat}, unless configuration property {@code exclidebinary=true} is
* set.</li>
* <li>for other types string representation is simply returned.</li>
* </ul>
Expand All @@ -81,27 +79,21 @@ public String toString() {
*/
public String convertToString(Object value) {
String result;

if (value == null) {
result = "NULL";
} else {

if (value instanceof Timestamp) {
result = new SimpleDateFormat(P6SpyOptions.getActiveInstance().getDatabaseDialectTimestampFormat()).format(value);
} else if (value instanceof Date) {
result = new SimpleDateFormat(P6SpyOptions.getActiveInstance().getDatabaseDialectDateFormat()).format(value);
} else if (value instanceof Boolean) {
if ("numeric".equals(P6SpyOptions.getActiveInstance().getDatabaseDialectBooleanFormat())) {
result = Boolean.FALSE.equals(value) ? "0" : "1";
} else {
result = value.toString();
}
} else if (value instanceof byte[]) {
if (value instanceof byte[]) {
// P6LogFactory may not be registered
P6LogLoadableOptions logOptions = P6LogOptions.getActiveInstance();
if (logOptions != null && logOptions.getExcludebinary()) {
result = "[binary]";
} else {
result = toHexString((byte[]) value);
BinaryFormat binaryFormat = P6SpyOptions.getActiveInstance().getDatabaseDialectBinaryFormatInstance();

// return early because BinaryFormat#toString wraps the value in quotes if needed
return binaryFormat.toString((byte[]) value);
}

// we should not do ((Blob) value).getBinaryStream(). ...
Expand All @@ -113,6 +105,16 @@ public String convertToString(Object value) {
// } else {
// result = value.toString();
// }
} else if (value instanceof Timestamp) {
result = new SimpleDateFormat(P6SpyOptions.getActiveInstance().getDatabaseDialectTimestampFormat()).format(value);
} else if (value instanceof Date) {
result = new SimpleDateFormat(P6SpyOptions.getActiveInstance().getDatabaseDialectDateFormat()).format(value);
} else if (value instanceof Boolean) {
if ("numeric".equals(P6SpyOptions.getActiveInstance().getDatabaseDialectBooleanFormat())) {
result = Boolean.FALSE.equals(value) ? "0" : "1";
} else {
result = value.toString();
}
} else {
result = value.toString();
}
Expand All @@ -123,23 +125,6 @@ public String convertToString(Object value) {
return result;
}

/**
* @param bytes
* the bytes value to convert to {@link String}
* @return the hexadecimal {@link String} representation of the given
* {@code bytes}.
*/
private String toHexString(byte[] bytes) {
char[] result = new char[bytes.length * 2];
int idx = 0;
for (byte b : bytes) {
int temp = (int) b & 0xFF;
result[idx++] = HEX_CHARS[temp / 16];
result[idx++] = HEX_CHARS[temp % 16];
}
return new String(result);
}

/**
* Qoutes the passed {@code stringValue} if it's needed.
*
Expand All @@ -153,7 +138,8 @@ private String quoteIfNeeded(String stringValue, Object obj) {
}

/*
* The following types do not get quoted: numeric, boolean
* The following types do not get quoted: numeric, boolean.
* Binary data is quoted only if the supplied binaryFormat requires that.
*
* It is tempting to use ParameterMetaData.getParameterType() for this
* purpose as it would be safer. However, this method will fail with some
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/p6spy/engine/logging/format/BinaryFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* P6Spy
*
* Copyright (C) 2002 - 2020 P6Spy
*
* 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.p6spy.engine.logging.format;

public interface BinaryFormat {
/**
* Transforms the supplied binary data to a string representation.
* Wraps the value in quotes if the database dialect requires them.
*
* @param input
* the binary data input value to convert to {@link String}
* @return
* the {@link String} representation of the given bytes
*/
public String toString(byte[] input);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* P6Spy
*
* Copyright (C) 2002 - 2020 P6Spy
*
* 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.p6spy.engine.logging.format;

/**
* Transforms binary data to hex encoded strings.
*/
public class HexEncodedBinaryFormat implements BinaryFormat {
private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
'F' };

/**
* The space needed for the opening and closing quote character.
*/
private static final int QUOTE_COUNT = 2;

@Override
public String toString(byte[] input) {
char[] result = new char[QUOTE_COUNT + input.length * 2];
int i = 0;
result[i++] = '\''; // add opening quote
hexEncode(input, result, i);
result[result.length - 1] = '\''; // add closing quote
return new String(result);
}

/**
* Hex encodes the supplied input bytes to the supplied output array.
* Writes two {@code char}s of output for every {@code byte} of input.
* @param input the input array
* @param output the output array
* @param outputOffset the offset of the output array to start writing from
*/
static void hexEncode(byte[] input, char[] output, int outputOffset) {
int idx = outputOffset;
for (byte b : input) {
int temp = (int) b & 0xFF;
output[idx++] = HEX_CHARS[temp / 16];
output[idx++] = HEX_CHARS[temp % 16];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* P6Spy
*
* Copyright (C) 2002 - 2020 P6Spy
*
* 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.p6spy.engine.logging.format;

/**
* Transforms binary data to MySQL hex encoded strings, for example {@code 0x8088BAD639F}.
* Such strings are not quoted when used in queries.
*
* @see <a href="https://dev.mysql.com/doc/refman/5.6/en/hexadecimal-literals.html">MySQL documentation</a>
*/
public class MySQLBinaryFormat implements BinaryFormat {

/**
* Reserve space for the two prefix chars 0 and x
*/
private static final int PREFIX_LENGTH = 2;

@Override
public String toString(byte[] input) {

char[] result = new char[PREFIX_LENGTH + input.length * 2];
int i = 0;
result[i++] = '0';
result[i++] = 'x';
HexEncodedBinaryFormat.hexEncode(input, result, i);
return new String(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* P6Spy
*
* Copyright (C) 2002 - 2020 P6Spy
*
* 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.p6spy.engine.logging.format;

/**
* Transforms binary data to PostgreSQL hex encoded strings, for example {@code \x8088BAD639F}
*
* @see <a href="https://www.postgresql.org/docs/current/datatype-binary.html#id-1.5.7.12.9">PostgreSQL documentation</a>
*/
public class PostgreSQLBinaryFormat implements BinaryFormat {

/**
* Reserve space for the two prefix chars \ and x
*/
private static final int PREFIX_LENGTH = 2;

/**
* The space needed for the opening and closing quote character.
*/
private static final int QUOTE_COUNT = 2;

@Override
public String toString(byte[] input) {
char[] result = new char[PREFIX_LENGTH + QUOTE_COUNT + input.length * 2];
int i = 0;
result[i++] = '\''; // opening quote
result[i++] = '\\'; // PostgreSQL binary...
result[i++] = 'x'; // ...data prefix
HexEncodedBinaryFormat.hexEncode(input, result, i);
result[result.length - 1] = '\''; // closing quote
return new String(result);
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/p6spy/engine/spy/P6SpyLoadableOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.util.Set;

import com.p6spy.engine.logging.format.BinaryFormat;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
import com.p6spy.engine.spy.appender.P6Logger;

Expand All @@ -37,6 +38,12 @@ public interface P6SpyLoadableOptions extends P6LoadableOptions, P6SpyOptionsMBe
void setAppend(String append);

P6Logger getAppenderInstance();

/**
* Gets an instance of the database dialect {@link BinaryFormat} the implementing class of which is
* set by {@link P6SpyOptionsMBean#setDatabaseDialectBinaryFormat}.
*/
BinaryFormat getDatabaseDialectBinaryFormatInstance();

MessageFormattingStrategy getLogMessageFormatInstance();

Expand Down
Loading

0 comments on commit fc1d70e

Please sign in to comment.