[#5246] Add an org.jooq.Allow.PlainSQL annotation and a PlainSQLChecker using JSR-308 and the checker framework
This commit is contained in:
parent
01bece477c
commit
8897304d42
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2009-2016, Data Geekery GmbH (http://www.datageekery.com)
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Other licenses:
|
||||
* -----------------------------------------------------------------------------
|
||||
* Commercial licenses for this work are available. These replace the above
|
||||
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
|
||||
* database integrations.
|
||||
*
|
||||
* For more information, please visit: http://www.jooq.org/licenses
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
package org.jooq.checker;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.checkerframework.framework.source.Result;
|
||||
import org.checkerframework.framework.source.SourceChecker;
|
||||
|
||||
/**
|
||||
* Common base class for checkers.
|
||||
*
|
||||
* @author Lukas Eder
|
||||
*/
|
||||
abstract class AbstractChecker extends SourceChecker {
|
||||
|
||||
void error(Object node, String message) {
|
||||
getChecker().report(Result.failure(message, node), node);
|
||||
}
|
||||
|
||||
void print(Printer printer) {
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter("error.txt"))){
|
||||
writer.println("This is probably a bug in jOOQ-checker.");
|
||||
writer.println("Please report this bug here: https://github.com/jOOQ/jOOQ/issues/new");
|
||||
writer.println("---------------------------------------------------------------------");
|
||||
|
||||
printer.print(writer);
|
||||
}
|
||||
catch (IOException ignore) {}
|
||||
}
|
||||
|
||||
interface Printer {
|
||||
void print(PrintWriter writer);
|
||||
}
|
||||
}
|
||||
111
jOOQ-checker/src/main/java/org/jooq/checker/PlainSQLChecker.java
Normal file
111
jOOQ-checker/src/main/java/org/jooq/checker/PlainSQLChecker.java
Normal file
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2009-2016, Data Geekery GmbH (http://www.datageekery.com)
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Other licenses:
|
||||
* -----------------------------------------------------------------------------
|
||||
* Commercial licenses for this work are available. These replace the above
|
||||
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
|
||||
* database integrations.
|
||||
*
|
||||
* For more information, please visit: http://www.jooq.org/licenses
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
package org.jooq.checker;
|
||||
|
||||
import static com.sun.source.util.TreePath.getPath;
|
||||
import static org.checkerframework.javacutil.TreeUtils.elementFromDeclaration;
|
||||
import static org.checkerframework.javacutil.TreeUtils.elementFromUse;
|
||||
import static org.checkerframework.javacutil.TreeUtils.enclosingMethod;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
import org.jooq.Allow;
|
||||
import org.jooq.PlainSQL;
|
||||
|
||||
import org.checkerframework.framework.source.SourceVisitor;
|
||||
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
|
||||
/**
|
||||
* A checker to disallow usage of {@link PlainSQL} API, except where allowed
|
||||
* explicitly.
|
||||
*
|
||||
* @author Lukas Eder
|
||||
*/
|
||||
public class PlainSQLChecker extends AbstractChecker {
|
||||
|
||||
@Override
|
||||
protected SourceVisitor<Void, Void> createSourceVisitor() {
|
||||
return new SourceVisitor<Void, Void>(getChecker()) {
|
||||
|
||||
@Override
|
||||
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
|
||||
try {
|
||||
ExecutableElement elementFromUse = elementFromUse(node);
|
||||
PlainSQL plainSQL = elementFromUse.getAnnotation(PlainSQL.class);
|
||||
|
||||
// In the absence of a @PlainSQL annotation,
|
||||
// all jOOQ API method calls will type check.
|
||||
if (plainSQL != null) {
|
||||
Element enclosing = elementFromDeclaration(enclosingMethod(getPath(root, node)));
|
||||
boolean allowed = false;
|
||||
|
||||
moveUpEnclosingLoop:
|
||||
while (enclosing != null) {
|
||||
if (enclosing.getAnnotation(Allow.PlainSQL.class) != null) {
|
||||
allowed = true;
|
||||
break moveUpEnclosingLoop;
|
||||
}
|
||||
|
||||
enclosing = enclosing.getEnclosingElement();
|
||||
}
|
||||
|
||||
if (!allowed)
|
||||
error(node, "Plain SQL usage not allowed at current scope. Use @Allow.PlainSQL.");
|
||||
}
|
||||
}
|
||||
catch (final Exception e) {
|
||||
print(new Printer() {
|
||||
@Override
|
||||
public void print(PrintWriter t) {
|
||||
e.printStackTrace(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return super.visitMethodInvocation(node, p);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -46,8 +46,6 @@ import static org.checkerframework.javacutil.TreeUtils.elementFromDeclaration;
|
||||
import static org.checkerframework.javacutil.TreeUtils.elementFromUse;
|
||||
import static org.checkerframework.javacutil.TreeUtils.enclosingMethod;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.EnumSet;
|
||||
|
||||
@ -59,8 +57,6 @@ import org.jooq.Require;
|
||||
import org.jooq.SQLDialect;
|
||||
import org.jooq.Support;
|
||||
|
||||
import org.checkerframework.framework.source.Result;
|
||||
import org.checkerframework.framework.source.SourceChecker;
|
||||
import org.checkerframework.framework.source.SourceVisitor;
|
||||
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
@ -71,11 +67,11 @@ import com.sun.source.tree.MethodInvocationTree;
|
||||
*
|
||||
* @author Lukas Eder
|
||||
*/
|
||||
public class SQLDialectChecker extends SourceChecker {
|
||||
public class SQLDialectChecker extends AbstractChecker {
|
||||
|
||||
@Override
|
||||
protected SourceVisitor<?, ?> createSourceVisitor() {
|
||||
return new SourceVisitor<Void, Void>(this) {
|
||||
protected SourceVisitor<Void, Void> createSourceVisitor() {
|
||||
return new SourceVisitor<Void, Void>(getChecker()) {
|
||||
|
||||
@Override
|
||||
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
|
||||
@ -134,23 +130,4 @@ public class SQLDialectChecker extends SourceChecker {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void error(Object node, String message) {
|
||||
getChecker().report(Result.failure(message, node), node);
|
||||
}
|
||||
|
||||
void print(Printer printer) {
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter("error.txt"))){
|
||||
writer.println("This is probably a bug in jOOQ-checker.");
|
||||
writer.println("Please report this bug here: https://github.com/jOOQ/jOOQ/issues/new");
|
||||
writer.println("---------------------------------------------------------------------");
|
||||
|
||||
printer.print(writer);
|
||||
}
|
||||
catch (IOException ignore) {}
|
||||
}
|
||||
|
||||
interface Printer {
|
||||
void print(PrintWriter writer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,6 +60,7 @@
|
||||
<fork>true</fork>
|
||||
<annotationProcessors>
|
||||
<annotationProcessor>org.jooq.checker.SQLDialectChecker</annotationProcessor>
|
||||
<annotationProcessor>org.jooq.checker.PlainSQLChecker</annotationProcessor>
|
||||
</annotationProcessors>
|
||||
<compilerArgs>
|
||||
<arg>-Xbootclasspath/p:1.8</arg>
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package org.jooq.example.checker;
|
||||
|
||||
import org.jooq.Allow;
|
||||
import org.jooq.impl.DSL;
|
||||
|
||||
// This class allows for using PlainSQL API
|
||||
@Allow.PlainSQL
|
||||
class PlainSQLCheckerTests1 {
|
||||
public static void compiles() {
|
||||
DSL.field("plain SQL");
|
||||
}
|
||||
}
|
||||
|
||||
// This class doesn't allow for using PlainSQL API
|
||||
class PlainSQLCheckerTests2 {
|
||||
public static void doesntCompile() {
|
||||
DSL.field("plain SQL");
|
||||
}
|
||||
|
||||
@Allow.PlainSQL
|
||||
public static void compiles() {
|
||||
DSL.field("plain SQL");
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ import org.jooq.impl.DSL;
|
||||
// The class requires both H2 and MySQL
|
||||
// The inherited @Allow annotation from the package allows only MySQL, though.
|
||||
@Require({ SQLDialect.H2, SQLDialect.MYSQL })
|
||||
public class CheckerTests {
|
||||
public class SQLDialectCheckerTests {
|
||||
|
||||
// @Allow = MySQL (inherited from package)
|
||||
// @Require = { H2, MySQL } (inherited from class)
|
||||
@ -1,47 +0,0 @@
|
||||
package org.jooq.example.checker;
|
||||
import static org.jooq.example.checker.db.h2.Tables.AUTHOR;
|
||||
import static org.jooq.example.checker.db.h2.Tables.BOOK;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
|
||||
import org.jooq.Require;
|
||||
import org.jooq.Result;
|
||||
import org.jooq.SQLDialect;
|
||||
import org.jooq.impl.DSL;
|
||||
|
||||
/**
|
||||
* @author Lukas Eder
|
||||
*/
|
||||
@Require(
|
||||
value = { SQLDialect.H2, SQLDialect.MYSQL }
|
||||
)
|
||||
public class ShouldntCompile {
|
||||
|
||||
@Require({SQLDialect.H2, SQLDialect.MYSQL})
|
||||
public static void main(String[] args) throws Exception {
|
||||
try (Connection c = DriverManager.getConnection("jdbc:h2:~/checker-test", "sa", "")) {
|
||||
Result<?> result =
|
||||
DSL.using(c)
|
||||
.select(
|
||||
AUTHOR.FIRST_NAME,
|
||||
AUTHOR.LAST_NAME,
|
||||
BOOK.ID,
|
||||
BOOK.TITLE,
|
||||
DSL.array(1)
|
||||
)
|
||||
.from(AUTHOR)
|
||||
.join(BOOK)
|
||||
.on(AUTHOR.ID.eq(BOOK.AUTHOR_ID))
|
||||
.connectBy("abc")
|
||||
.orderBy(BOOK.ID.asc())
|
||||
.fetch();
|
||||
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
|
||||
public static void x() {
|
||||
DSL.array(2);
|
||||
}
|
||||
}
|
||||
@ -130,6 +130,9 @@ import java.lang.annotation.Target;
|
||||
* {@link SQLDialect#predecessor()}, and its {@link SQLDialect#family()} are
|
||||
* allowed.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Apart from the above main purpose, the {@link Allow} annotation also serves
|
||||
* as a semantic namespace for other annotations, such as {@link Allow.PlainSQL}
|
||||
*
|
||||
* @author Lukas Eder
|
||||
* @see Require
|
||||
@ -172,4 +175,20 @@ public @interface Allow {
|
||||
POSTGRES,
|
||||
SQLITE,
|
||||
};
|
||||
|
||||
/**
|
||||
* This annotation allows {@link PlainSQL} API usage within the scope of
|
||||
* where it is placed.
|
||||
* <p>
|
||||
* Type checking for these annotations can be supplied by
|
||||
* <code>org.jooq.checker.PlainSQLChecker</code> from the jOOQ-checker
|
||||
* module.
|
||||
*
|
||||
* @author Lukas Eder
|
||||
*/
|
||||
@Target({ METHOD, CONSTRUCTOR, TYPE, PACKAGE })
|
||||
@Retention(RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@interface PlainSQL {}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user