Skip to content

Instantly share code, notes, and snippets.

@RichardBradley
Created October 28, 2017 13:44
Show Gist options
  • Save RichardBradley/676ef35db100532bbc32a3b2344a1723 to your computer and use it in GitHub Desktop.
Save RichardBradley/676ef35db100532bbc32a3b2344a1723 to your computer and use it in GitHub Desktop.
JooqConfig to work around https://github.com/jOOQ/jOOQ/issues/4477
package my.app;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.AppenderRef;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.TableField;
import org.jooq.conf.RenderNameStyle;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.tools.jdbc.JDBCUtils;
import org.springframework.beans.factory.InitializingBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import static com.google.common.base.Preconditions.checkArgument;
public class JooqConfig extends DefaultConfiguration implements InitializingBean {
private Method recordGetFieldMethod = Record.class.getMethod("get", Field.class);
public JooqConfig() throws Exception {
super();
// The default is to add quotes to all table names, which makes them
// case-sensitive, which we don't want.
settings()
.setRenderNameStyle(RenderNameStyle.AS_IS);
}
@Override
public void afterPropertiesSet() throws Exception {
Connection connection = connectionProvider().acquire();
try {
setSQLDialect(JDBCUtils.dialect(connection));
} finally {
connectionProvider().release(connection);
}
// We attach a LoggerListener to throw for cases not detected by `wrapRecord`
// (`wrapRecord` detects some cases that the logs do not warn about, so we need
// both)
//
// See http://logging.apache.org/log4j/2.x/manual/customconfig.html#AddingToCurrent
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
JooqLoggerListener appender = new JooqLoggerListener();
appender.start();
config.addAppender(appender);
AppenderRef ref = AppenderRef.createAppenderRef(appender.getName(), null, null);
AppenderRef[] refs = {ref};
LoggerConfig loggerConfig = LoggerConfig.createLogger(
true, Level.INFO, "org.jooq.impl",
"true", refs, null, config, null);
loggerConfig.addAppender(appender, null, null);
config.addLogger("org.jooq.impl", loggerConfig);
ctx.updateLoggers();
}
/**
* Wraps a JOOQ record to guard against https://github.com/jOOQ/jOOQ/issues/4477
*/
public Record wrapRecord(Record r) {
return (Record) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[]{Record.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.equals(recordGetFieldMethod)) {
// super() is:
// return this.get(Tools.indexOrFail(this.fieldsRow(), field));
checkArgument(args.length == 1);
Field<?> argField = (Field<?>) args[0];
// We can't directly call "Tools.indexOrFail(this.fieldsRow(), field)"
// but idx here has the same value (if >= 0):
int idx = r.fieldsRow().indexOf(argField);
if (idx >= 0) {
Field<?> rowField = r.fieldsRow().field(idx);
if (argField instanceof TableField && rowField instanceof TableField) {
String argTableName = ((TableField) argField).getTable().getName();
String rowTableName = ((TableField) rowField).getTable().getName();
if (argField.getName().equals(rowField.getName())
&& !argTableName.equals(rowTableName)) {
throw new IllegalArgumentException(String.format(
"Invalid row access: requested %s from record which has only %s",
argField,
rowField));
}
}
}
}
try {
return method.invoke(r, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
});
}
/**
* A class that will listen for Jooq warning logs that are actually pretty serious
* and should cause runtime errors (rather than incorrect data).
*
* See https://github.com/jOOQ/jOOQ/issues/4477
*/
private static class JooqLoggerListener extends AbstractAppender {
JooqLoggerListener() {
super("JooqLoggerListener", null, null, false);
}
@Override
public void append(LogEvent logEvent) {
String message = logEvent.getMessage().getFormat();
if (message != null) {
if (message.contains("Ambiguous match")) {
throw new java.lang.IllegalStateException(
"An 'Ambiguous match' warning from JOOQ "
+ "can mean that it is using the wrong value in a field get():\n "
+ message);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment