1
0
mirror of https://github.com/russ-p/external-sql.git synced 2025-12-14 01:14:24 +00:00
This commit is contained in:
2019-10-05 12:30:54 +03:00
commit b1cf8e0e7e
8 changed files with 293 additions and 0 deletions

52
pom.xml Normal file
View File

@@ -0,0 +1,52 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.russ-p</groupId>
<artifactId>external-sql</artifactId>
<packaging>jar</packaging>
<version>0.1-SNAPSHOT</version>
<name>external-sql</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<bytebuddy.version>1.9.10</bytebuddy.version>
<assertj.version>3.11.1</assertj.version>
<junit.version>4.12</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${bytebuddy.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,17 @@
package com.github.russ_p.externalsql;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface ExternalSQL {
/**
* file with sql queries
*/
String value();
}

View File

@@ -0,0 +1,44 @@
package com.github.russ_p.externalsql;
import java.io.IOException;
import java.util.Objects;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional;
import net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
public class ExternalSQLFactory {
@SuppressWarnings("unchecked")
public <T> T create(Class<T> clazz) throws IOException, InstantiationException, IllegalAccessException {
ExternalSQL anno = clazz.getAnnotation(ExternalSQL.class);
Objects.requireNonNull(anno, "Class must be annotated with @ExternalSQL");
String srcFile = anno.value();
SqlResolver resolver = new SqlResolver(srcFile);
Optional<Object> implement = new ByteBuddy()
.subclass(Object.class)
.implement(clazz);
ReceiverTypeDefinition<Object> intercept = implement
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("SQL queries from " + srcFile));
for (String name : resolver.getQueryNames()) {
intercept = intercept
.method(ElementMatchers.named(name))
.intercept(FixedValue.value(resolver.getQuery(name)));
}
Class<?> dynamicType = intercept
.make()
.load(getClass().getClassLoader())
.getLoaded();
return (T) dynamicType.newInstance();
}
}

View File

@@ -0,0 +1,94 @@
package com.github.russ_p.externalsql;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
class SqlResolver {
private static final String NAME_COMMENT_PREFIX = "-- @";
private static final String COMMENT_PREFIX = "--";
private final Map<String, String> queries = new HashMap<>();
public SqlResolver(String filename) throws IOException {
this(new File(resolvePath(filename)));
}
public SqlResolver(File file) throws IOException {
parseFile(file);
}
public Set<String> getQueryNames() {
return queries.keySet();
}
public String getQuery(String name) {
return queries.getOrDefault(name, "");
}
private void put(String name, String query) {
if (name == null || name.isEmpty())
return;
if (query == null || query.isEmpty())
return;
queries.put(name, query);
}
private void parseFile(File file) throws IOException {
String queryName = "";
StringBuffer query = new StringBuffer();
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line = reader.readLine();
while (line != null) {
if (isSqlHeaderComment(line)) {
put(queryName, query.toString().trim());
queryName = getName(line);
query = new StringBuffer();
} else if (isSimpleComment(line)) {
} else {
query.append(line).append("\n");
}
// read next line
line = reader.readLine();
}
}
put(queryName, query.toString());
}
private boolean isSimpleComment(String line) {
return line.startsWith(COMMENT_PREFIX) && !isSqlHeaderComment(line);
}
private boolean isSqlHeaderComment(String line) {
return line.startsWith(NAME_COMMENT_PREFIX);
}
private String getName(String line) {
StringTokenizer tokenizer = new StringTokenizer(line, " ");
while (tokenizer.hasMoreElements()) {
String str = tokenizer.nextToken();
if (str.startsWith("@")) {
return str.substring(1);
}
}
throw new IllegalStateException("No sql query name found in line " + line);
}
private static String resolvePath(String path) {
if (path.startsWith("classpath:")) {
return SqlResolver.class.getClassLoader().getResource(path.substring(10)).getFile();
}
return path;
}
}

View File

@@ -0,0 +1,26 @@
package com.github.russ_p.externalsql;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Before;
import org.junit.Test;
public class ExternalSQLFactoryTest {
private ExternalSQLFactory factory;
@Before
public void setUp() throws Exception {
factory = new ExternalSQLFactory();
}
@Test
public void testCreate() throws Exception {
TestQueries testQueries = factory.create(TestQueries.class);
assertThat(testQueries.selectOne()).isNotEmpty();
assertThat(testQueries.selectOne()).isEqualTo("select * from test_table;");
assertThat(testQueries.selectTwo()).isNotEmpty();
}
}

View File

@@ -0,0 +1,34 @@
package com.github.russ_p.externalsql;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Before;
import org.junit.Test;
public class SqlResolverTest {
@Before
public void setUp() throws Exception {
}
@Test
public void testSqlResolverString() throws Exception {
SqlResolver resolver = new SqlResolver(
SqlResolverTest.class.getClassLoader().getResource("test.sql").getFile());
assertThat(resolver.getQueryNames()).hasSize(2);
assertThat(resolver.getQuery("selectOne")).isNotEmpty();
assertThat(resolver.getQuery("selectOne")).isEqualTo("select * from test_table;");
assertThat(resolver.getQuery("selectTwo")).isNotEmpty();
assertThat(resolver.getQuery("notExist")).isBlank();
}
@Test
public void testSqlResolverClasspathString() throws Exception {
SqlResolver resolver = new SqlResolver("classpath:test.sql");
assertThat(resolver).isNotNull();
}
}

View File

@@ -0,0 +1,9 @@
package com.github.russ_p.externalsql;
@ExternalSQL("classpath:test.sql")
public interface TestQueries {
String selectOne();
String selectTwo();
}

View File

@@ -0,0 +1,17 @@
-- Ext-SQL test file
-- @selectOne
select * from test_table;
-- @selectTwo
-- comment example
SELECT
a,
b,
FROM test_table
WHERE
a > 1;
-- end of file