/*
* (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core.storage.sql.jdbc;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.model.Delta;
import org.nuxeo.ecm.core.storage.sql.Model;
import org.nuxeo.ecm.core.storage.sql.Row;
import org.nuxeo.ecm.core.storage.sql.jdbc.db.Column;
/**
* Logger used for debugging.
*/
public class JDBCLogger {
public static final Log log = LogFactory.getLog(JDBCLogger.class);
public static final int DEBUG_MAX_STRING = 100;
public static final int DEBUG_MAX_ARRAY = 10;
public final String instance;
public JDBCLogger(String instance) {
this.instance = instance;
}
public boolean isLogEnabled() {
return log.isTraceEnabled();
}
public String formatMessage(String message) {
return "(" + instance + ") SQL: " + message;
}
public void error(String message) {
log.error(formatMessage(message));
}
public void error(String message, Throwable t) {
log.error(formatMessage(message), t);
}
public void warn(String message) {
log.warn(formatMessage(message));
}
public void info(String message) {
log.info(formatMessage(message));
}
public void log(String message) {
log.trace(formatMessage(message));
}
public void logCount(int count) {
if (count > 0 && isLogEnabled()) {
log(" -> " + count + " row" + (count > 1 ? "s" : ""));
}
}
public void logResultSet(ResultSet rs, List<Column> columns) throws SQLException {
List<String> res = new LinkedList<>();
int i = 0;
for (Column column : columns) {
i++;
Serializable v = column.getFromResultSet(rs, i);
res.add(column.getKey() + "=" + loggedValue(v));
}
log(" -> " + String.join(", ", res));
}
public void logMap(Map<String, Serializable> map) throws SQLException {
String result = map.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + loggedValue(entry.getValue()))
.collect(Collectors.joining(", "));
log(" -> " + result);
}
public void logMaps(List<Map<String, Serializable>> maps, boolean countTotal, long totalSize) {
List<Map<String, Serializable>> debugMaps = maps;
String end = "";
if (maps.size() > DEBUG_MAX_ARRAY) {
debugMaps = new ArrayList<>(DEBUG_MAX_ARRAY);
int i = 0;
for (Map<String, Serializable> map : maps) {
debugMaps.add(map);
i++;
if (i == DEBUG_MAX_ARRAY) {
break;
}
}
end = "...(" + maps.size() + " ids)...";
}
if (countTotal) {
end += " (total " + totalSize + ')';
}
String result = debugMaps.stream()
.map(map -> map.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + loggedValue(entry.getValue()))
.collect(Collectors.joining(", ")))
.collect(Collectors.joining(",", "{", "}"));
log(" -> " + result + end);
}
public void logIds(List<Serializable> ids, boolean countTotal, long totalSize) {
List<Serializable> debugIds = ids;
String end = "";
if (ids.size() > DEBUG_MAX_ARRAY) {
debugIds = new ArrayList<>(DEBUG_MAX_ARRAY);
int i = 0;
for (Serializable id : ids) {
debugIds.add(id);
i++;
if (i == DEBUG_MAX_ARRAY) {
break;
}
}
end = "...(" + ids.size() + " ids)...";
}
if (countTotal) {
end += " (total " + totalSize + ')';
}
log(" -> " + debugIds + end);
}
public void logSQL(String sql, List<Column> columns, Row row) {
logSQL(sql, columns, row, Collections.emptyList(), Collections.emptyMap());
}
public void logSQL(String sql, List<Column> columns, Row row, List<Column> whereColumns,
Map<String, Serializable> conditions) {
List<Serializable> values = new ArrayList<>();
for (Column column : columns) {
String key = column.getKey();
Serializable value = row.get(key);
if (value instanceof Delta) {
Delta delta = (Delta) value;
if (delta.getBase() != null) {
value = delta.getDeltaValue();
}
}
values.add(value);
}
for (Column column : whereColumns) {
String key = column.getKey();
Serializable value;
if (column.getKey().equals(Model.MAIN_KEY)) {
value = row.get(key);
} else {
value = conditions.get(key);
}
values.add(value);
}
logSQL(sql, values);
}
// callable statement with one return value
private static final String CALLABLE_START = "{?=";
public void logSQL(String sql, Collection<Serializable> values) {
StringBuilder buf = new StringBuilder();
int start = 0;
if (sql.startsWith(CALLABLE_START)) {
buf.append(CALLABLE_START);
start = CALLABLE_START.length();
}
for (Serializable v : values) {
int index = sql.indexOf('?', start);
if (index == -1) {
// mismatch between number of ? and number of values
break;
}
buf.append(sql, start, index);
buf.append(loggedValue(v));
start = index + 1;
}
buf.append(sql, start, sql.length());
log(buf.toString());
}
/**
* Returns a loggable value using pseudo-SQL syntax.
*/
@SuppressWarnings("boxing")
public static String loggedValue(Object value) {
if (value == null) {
return "NULL";
}
if (value instanceof String) {
String v = (String) value;
if (v.length() > DEBUG_MAX_STRING) {
v = v.substring(0, DEBUG_MAX_STRING) + "...(" + v.length() + " chars)...";
}
return "'" + v.replace("'", "''") + "'";
}
if (value instanceof Calendar) {
Calendar cal = (Calendar) value;
char sign;
int offset = cal.getTimeZone().getOffset(cal.getTimeInMillis()) / 60000;
if (offset < 0) {
offset = -offset;
sign = '-';
} else {
sign = '+';
}
return String.format("TIMESTAMP '%04d-%02d-%02dT%02d:%02d:%02d.%03d%c%02d:%02d'", cal.get(Calendar.YEAR), //
cal.get(Calendar.MONTH) + 1, //
cal.get(Calendar.DAY_OF_MONTH), //
cal.get(Calendar.HOUR_OF_DAY), //
cal.get(Calendar.MINUTE), //
cal.get(Calendar.SECOND), //
cal.get(Calendar.MILLISECOND), //
sign, offset / 60, offset % 60);
}
if (value instanceof java.sql.Date) {
return "DATE '" + value.toString() + "'";
}
if (value instanceof Object[]) {
Object[] v = (Object[]) value;
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; i < v.length; i++) {
if (i > 0) {
b.append(',');
if (i > DEBUG_MAX_ARRAY) {
b.append("...(").append(v.length).append(" items)...");
break;
}
}
b.append(loggedValue(v[i]));
}
b.append(']');
return b.toString();
}
return value.toString();
}
}