001/* 002 * (C) Copyright 2006-2011 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.Map.Entry; 032import java.util.Set; 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.Row; 039import org.nuxeo.ecm.core.storage.sql.jdbc.db.Column; 040 041/** 042 * Logger used for debugging. 043 */ 044public class JDBCLogger { 045 046 public static final Log log = LogFactory.getLog(JDBCLogger.class); 047 048 public static final int DEBUG_MAX_STRING = 100; 049 050 public static final int DEBUG_MAX_ARRAY = 10; 051 052 public final String instance; 053 054 public JDBCLogger(String instance) { 055 this.instance = instance; 056 } 057 058 public boolean isLogEnabled() { 059 return log.isTraceEnabled(); 060 } 061 062 public String formatMessage(String message) { 063 return "(" + instance + ") SQL: " + message; 064 } 065 066 public void error(String message) { 067 log.error(formatMessage(message)); 068 } 069 070 public void error(String message, Throwable t) { 071 log.error(formatMessage(message), t); 072 } 073 074 public void warn(String message) { 075 log.warn(formatMessage(message)); 076 } 077 078 public void info(String message) { 079 log.info(formatMessage(message)); 080 } 081 082 public void log(String message) { 083 log.trace(formatMessage(message)); 084 } 085 086 public void logCount(int count) { 087 if (count > 0 && isLogEnabled()) { 088 log(" -> " + count + " row" + (count > 1 ? "s" : "")); 089 } 090 } 091 092 public void logCounts(int[] counts) { 093 if (!isLogEnabled()) { 094 return; 095 } 096 int count = 0; 097 for (int c : counts) { 098 count += c; 099 } 100 logCount(count); 101 } 102 103 public void logResultSet(ResultSet rs, List<Column> columns) throws SQLException { 104 List<String> res = new LinkedList<String>(); 105 int i = 0; 106 for (Column column : columns) { 107 i++; 108 Serializable v = column.getFromResultSet(rs, i); 109 res.add(column.getKey() + "=" + loggedValue(v)); 110 } 111 log(" -> " + StringUtils.join(res, ", ")); 112 } 113 114 public void logMap(Map<String, Serializable> map) throws SQLException { 115 List<String> res = new LinkedList<String>(); 116 for (Entry<String, Serializable> en : map.entrySet()) { 117 res.add(en.getKey() + "=" + loggedValue(en.getValue())); 118 } 119 log(" -> " + StringUtils.join(res, ", ")); 120 } 121 122 public void logIds(List<Serializable> ids, boolean countTotal, long totalSize) { 123 List<Serializable> debugIds = ids; 124 String end = ""; 125 if (ids.size() > DEBUG_MAX_ARRAY) { 126 debugIds = new ArrayList<Serializable>(DEBUG_MAX_ARRAY); 127 int i = 0; 128 for (Serializable id : ids) { 129 debugIds.add(id); 130 i++; 131 if (i == DEBUG_MAX_ARRAY) { 132 break; 133 } 134 } 135 end = "...(" + ids.size() + " ids)..."; 136 } 137 if (countTotal) { 138 end += " (total " + totalSize + ')'; 139 } 140 log(" -> " + debugIds + end); 141 } 142 143 public void logSQL(String sql, List<Column> columns, Row row) { 144 logSQL(sql, columns, row, Collections.<String> emptySet()); 145 } 146 147 public void logSQL(String sql, List<Column> columns, Row row, Set<String> deltas) { 148 List<Serializable> values = new ArrayList<Serializable>(columns.size()); 149 for (Column column : columns) { 150 String key = column.getKey(); 151 Serializable value = row.get(key); 152 if (deltas.contains(key)) { 153 value = ((Delta) value).getDeltaValue(); 154 } 155 values.add(value); 156 } 157 logSQL(sql, values); 158 } 159 160 // callable statement with one return value 161 private static final String CALLABLE_START = "{?="; 162 163 public void logSQL(String sql, Collection<Serializable> values) { 164 StringBuilder buf = new StringBuilder(); 165 int start = 0; 166 if (sql.startsWith(CALLABLE_START)) { 167 buf.append(CALLABLE_START); 168 start = CALLABLE_START.length(); 169 } 170 for (Serializable v : values) { 171 int index = sql.indexOf('?', start); 172 if (index == -1) { 173 // mismatch between number of ? and number of values 174 break; 175 } 176 buf.append(sql, start, index); 177 buf.append(loggedValue(v)); 178 start = index + 1; 179 } 180 buf.append(sql, start, sql.length()); 181 log(buf.toString()); 182 } 183 184 /** 185 * Returns a loggable value using pseudo-SQL syntax. 186 */ 187 @SuppressWarnings("boxing") 188 public static String loggedValue(Object value) { 189 if (value == null) { 190 return "NULL"; 191 } 192 if (value instanceof String) { 193 String v = (String) value; 194 if (v.length() > DEBUG_MAX_STRING) { 195 v = v.substring(0, DEBUG_MAX_STRING) + "...(" + v.length() + " chars)..."; 196 } 197 return "'" + v.replace("'", "''") + "'"; 198 } 199 if (value instanceof Calendar) { 200 Calendar cal = (Calendar) value; 201 char sign; 202 int offset = cal.getTimeZone().getOffset(cal.getTimeInMillis()) / 60000; 203 if (offset < 0) { 204 offset = -offset; 205 sign = '-'; 206 } else { 207 sign = '+'; 208 } 209 return String.format("TIMESTAMP '%04d-%02d-%02dT%02d:%02d:%02d.%03d%c%02d:%02d'", cal.get(Calendar.YEAR), // 210 cal.get(Calendar.MONTH) + 1, // 211 cal.get(Calendar.DAY_OF_MONTH), // 212 cal.get(Calendar.HOUR_OF_DAY), // 213 cal.get(Calendar.MINUTE), // 214 cal.get(Calendar.SECOND), // 215 cal.get(Calendar.MILLISECOND), // 216 sign, offset / 60, offset % 60); 217 } 218 if (value instanceof java.sql.Date) { 219 return "DATE '" + value.toString() + "'"; 220 } 221 if (value instanceof Object[]) { 222 Object[] v = (Object[]) value; 223 StringBuilder b = new StringBuilder(); 224 b.append('['); 225 for (int i = 0; i < v.length; i++) { 226 if (i > 0) { 227 b.append(','); 228 if (i > DEBUG_MAX_ARRAY) { 229 b.append("...(" + v.length + " items)..."); 230 break; 231 } 232 } 233 b.append(loggedValue(v[i])); 234 } 235 b.append(']'); 236 return b.toString(); 237 } 238 return value.toString(); 239 } 240}