001/* 002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.core.storage.sql.jdbc; 020 021import java.io.Serializable; 022import java.sql.ResultSet; 023import java.sql.SQLException; 024import java.util.ArrayList; 025import java.util.Calendar; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032import java.util.stream.Collectors; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.nuxeo.common.utils.StringUtils; 037import org.nuxeo.ecm.core.api.model.Delta; 038import org.nuxeo.ecm.core.storage.sql.Model; 039import org.nuxeo.ecm.core.storage.sql.Row; 040import org.nuxeo.ecm.core.storage.sql.jdbc.db.Column; 041 042/** 043 * Logger used for debugging. 044 */ 045public class JDBCLogger { 046 047 public static final Log log = LogFactory.getLog(JDBCLogger.class); 048 049 public static final int DEBUG_MAX_STRING = 100; 050 051 public static final int DEBUG_MAX_ARRAY = 10; 052 053 public final String instance; 054 055 public JDBCLogger(String instance) { 056 this.instance = instance; 057 } 058 059 public boolean isLogEnabled() { 060 return log.isTraceEnabled(); 061 } 062 063 public String formatMessage(String message) { 064 return "(" + instance + ") SQL: " + message; 065 } 066 067 public void error(String message) { 068 log.error(formatMessage(message)); 069 } 070 071 public void error(String message, Throwable t) { 072 log.error(formatMessage(message), t); 073 } 074 075 public void warn(String message) { 076 log.warn(formatMessage(message)); 077 } 078 079 public void info(String message) { 080 log.info(formatMessage(message)); 081 } 082 083 public void log(String message) { 084 log.trace(formatMessage(message)); 085 } 086 087 public void logCount(int count) { 088 if (count > 0 && isLogEnabled()) { 089 log(" -> " + count + " row" + (count > 1 ? "s" : "")); 090 } 091 } 092 093 public void logResultSet(ResultSet rs, List<Column> columns) throws SQLException { 094 List<String> res = new LinkedList<>(); 095 int i = 0; 096 for (Column column : columns) { 097 i++; 098 Serializable v = column.getFromResultSet(rs, i); 099 res.add(column.getKey() + "=" + loggedValue(v)); 100 } 101 log(" -> " + String.join(", ", res)); 102 } 103 104 public void logMap(Map<String, Serializable> map) throws SQLException { 105 String result = map.entrySet() 106 .stream() 107 .map(entry -> entry.getKey() + "=" + loggedValue(entry.getValue())) 108 .collect(Collectors.joining(", ")); 109 log(" -> " + result); 110 } 111 112 public void logIds(List<Serializable> ids, boolean countTotal, long totalSize) { 113 List<Serializable> debugIds = ids; 114 String end = ""; 115 if (ids.size() > DEBUG_MAX_ARRAY) { 116 debugIds = new ArrayList<>(DEBUG_MAX_ARRAY); 117 int i = 0; 118 for (Serializable id : ids) { 119 debugIds.add(id); 120 i++; 121 if (i == DEBUG_MAX_ARRAY) { 122 break; 123 } 124 } 125 end = "...(" + ids.size() + " ids)..."; 126 } 127 if (countTotal) { 128 end += " (total " + totalSize + ')'; 129 } 130 log(" -> " + debugIds + end); 131 } 132 133 public void logSQL(String sql, List<Column> columns, Row row) { 134 logSQL(sql, columns, row, Collections.emptyList(), Collections.emptyMap()); 135 } 136 137 public void logSQL(String sql, List<Column> columns, Row row, List<Column> whereColumns, 138 Map<String, Serializable> conditions) { 139 List<Serializable> values = new ArrayList<>(); 140 for (Column column : columns) { 141 String key = column.getKey(); 142 Serializable value = row.get(key); 143 if (value instanceof Delta) { 144 Delta delta = (Delta) value; 145 if (delta.getBase() != null) { 146 value = delta.getDeltaValue(); 147 } 148 } 149 values.add(value); 150 } 151 for (Column column : whereColumns) { 152 String key = column.getKey(); 153 Serializable value; 154 if (column.getKey().equals(Model.MAIN_KEY)) { 155 value = row.get(key); 156 } else { 157 value = conditions.get(key); 158 } 159 values.add(value); 160 } 161 logSQL(sql, values); 162 } 163 164 // callable statement with one return value 165 private static final String CALLABLE_START = "{?="; 166 167 public void logSQL(String sql, Collection<Serializable> values) { 168 StringBuilder buf = new StringBuilder(); 169 int start = 0; 170 if (sql.startsWith(CALLABLE_START)) { 171 buf.append(CALLABLE_START); 172 start = CALLABLE_START.length(); 173 } 174 for (Serializable v : values) { 175 int index = sql.indexOf('?', start); 176 if (index == -1) { 177 // mismatch between number of ? and number of values 178 break; 179 } 180 buf.append(sql, start, index); 181 buf.append(loggedValue(v)); 182 start = index + 1; 183 } 184 buf.append(sql, start, sql.length()); 185 log(buf.toString()); 186 } 187 188 /** 189 * Returns a loggable value using pseudo-SQL syntax. 190 */ 191 @SuppressWarnings("boxing") 192 public static String loggedValue(Object value) { 193 if (value == null) { 194 return "NULL"; 195 } 196 if (value instanceof String) { 197 String v = (String) value; 198 if (v.length() > DEBUG_MAX_STRING) { 199 v = v.substring(0, DEBUG_MAX_STRING) + "...(" + v.length() + " chars)..."; 200 } 201 return "'" + v.replace("'", "''") + "'"; 202 } 203 if (value instanceof Calendar) { 204 Calendar cal = (Calendar) value; 205 char sign; 206 int offset = cal.getTimeZone().getOffset(cal.getTimeInMillis()) / 60000; 207 if (offset < 0) { 208 offset = -offset; 209 sign = '-'; 210 } else { 211 sign = '+'; 212 } 213 return String.format("TIMESTAMP '%04d-%02d-%02dT%02d:%02d:%02d.%03d%c%02d:%02d'", cal.get(Calendar.YEAR), // 214 cal.get(Calendar.MONTH) + 1, // 215 cal.get(Calendar.DAY_OF_MONTH), // 216 cal.get(Calendar.HOUR_OF_DAY), // 217 cal.get(Calendar.MINUTE), // 218 cal.get(Calendar.SECOND), // 219 cal.get(Calendar.MILLISECOND), // 220 sign, offset / 60, offset % 60); 221 } 222 if (value instanceof java.sql.Date) { 223 return "DATE '" + value.toString() + "'"; 224 } 225 if (value instanceof Object[]) { 226 Object[] v = (Object[]) value; 227 StringBuilder b = new StringBuilder(); 228 b.append('['); 229 for (int i = 0; i < v.length; i++) { 230 if (i > 0) { 231 b.append(','); 232 if (i > DEBUG_MAX_ARRAY) { 233 b.append("...(").append(v.length).append(" items)..."); 234 break; 235 } 236 } 237 b.append(loggedValue(v[i])); 238 } 239 b.append(']'); 240 return b.toString(); 241 } 242 return value.toString(); 243 } 244}