Created
October 28, 2017 13:44
-
-
Save RichardBradley/676ef35db100532bbc32a3b2344a1723 to your computer and use it in GitHub Desktop.
JooqConfig to work around https://github.com/jOOQ/jOOQ/issues/4477
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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