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}