/*
* 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.
*/
package org.jdbi.v3.core.locator;
import java.io.IOException;
import java.io.InputStream;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.antlr.runtime.ANTLRInputStream;
import org.jdbi.v3.core.internal.SqlScriptParser;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
Locates SQL in .sql
files on the classpath. Given a class and method name, for example com.foo.Bar#query
, load a classpath resource name like com/foo/Bar/query.sql
. The contents are then parsed, cached, and returned for use by a statement. /**
* Locates SQL in {@code .sql} files on the classpath. Given a class and
* method name, for example {@code com.foo.Bar#query}, load a
* classpath resource name like {@code com/foo/Bar/query.sql}.
* The contents are then parsed, cached, and returned for use by a statement.
*/
public final class ClasspathSqlLocator {
private static final char PACKAGE_DELIMITER = '.';
private static final char PATH_DELIMITER = '/';
private static final SqlScriptParser SQL_SCRIPT_PARSER = new SqlScriptParser((t, sb) -> sb.append(t.getText()));
@SuppressWarnings("unchecked")
private static final Map<Entry<ClassLoader, String>, String> CACHE = ExpiringMap.builder()
.expiration(10, TimeUnit.MINUTES)
.expirationPolicy(ExpirationPolicy.ACCESSED)
.entryLoader(obj -> {
Entry<ClassLoader, String> entry = (Entry<ClassLoader, String>) obj;
return readResource(entry.getKey(), entry.getValue());
})
.build();
private static final String SQL_EXTENSION = ".sql";
private ClasspathSqlLocator() {}
Locates SQL for the given type and name. Example: Given a type com.foo.Bar
and a name of
baz
, looks for a resource named com/foo/Bar/baz.sql
on the classpath and returns its
contents as a String.
Params: - type – the type that "owns" the given SQL. Dictates the directory path to the SQL resource file on the
classpath.
- name – the SQL statement name (usually a method or field name from the type).
Returns: the located SQL.
/**
* Locates SQL for the given type and name. Example: Given a type <code>com.foo.Bar</code> and a name of
* <code>baz</code>, looks for a resource named <code>com/foo/Bar/baz.sql</code> on the classpath and returns its
* contents as a String.
*
* @param type the type that "owns" the given SQL. Dictates the directory path to the SQL resource file on the
* classpath.
* @param name the SQL statement name (usually a method or field name from the type).
* @return the located SQL.
*/
public static String findSqlOnClasspath(Class<?> type, String name) {
String path = resourcePathFor(type, name);
return getResourceOnClasspath(type.getClassLoader(), path);
}
Locates SQL for the given fully-qualified name. Example: Given the name com.foo.Bar.baz
, looks for
a resource named com/foo/Bar/baz.sql
on the classpath and returns its contents as a String.
Params: - name – fully qualified name.
Returns: the located SQL.
/**
* Locates SQL for the given fully-qualified name. Example: Given the name <code>com.foo.Bar.baz</code>, looks for
* a resource named <code>com/foo/Bar/baz.sql</code> on the classpath and returns its contents as a String.
*
* @param name fully qualified name.
* @return the located SQL.
*/
public static String findSqlOnClasspath(String name) {
String path = resourcePathFor(name);
return getResourceOnClasspath(selectClassLoader(), path);
}
private static String resourcePathFor(Class<?> extensionType, String methodName) {
return resourcePathFor(extensionType.getName() + "." + methodName);
}
private static String resourcePathFor(String fullyQualifiedName) {
return fullyQualifiedName.replace(PACKAGE_DELIMITER, PATH_DELIMITER) + SQL_EXTENSION;
}
Returns resource's contents as a string at the specified path. The path should point directly
to the resource at the classpath. The resource is loaded by the current thread's classloader.
Params: - path – the resource path
See Also: Returns: the resource's contents
/**
* Returns resource's contents as a string at the specified path. The path should point directly
* to the resource at the classpath. The resource is loaded by the current thread's classloader.
*
* @param path the resource path
* @return the resource's contents
* @see ClassLoader#getResource(String)
*/
public static String getResourceOnClasspath(String path) {
return getResourceOnClasspath(selectClassLoader(), path);
}
Returns resource's contents as a string at the specified path by the specified classloader.
The path should point directly to the resource at the classpath. The classloader should have
access to the resource.
Params: - classLoader – the classloader which loads the resource
- path – the resource path
See Also: Returns: the resource's contents
/**
* Returns resource's contents as a string at the specified path by the specified classloader.
* The path should point directly to the resource at the classpath. The classloader should have
* access to the resource.
*
* @param classLoader the classloader which loads the resource
* @param path the resource path
* @return the resource's contents
* @see ClassLoader#getResource(String)
*/
public static String getResourceOnClasspath(ClassLoader classLoader, String path) {
return CACHE.get(new AbstractMap.SimpleEntry<>(classLoader, path));
}
private static String readResource(ClassLoader classLoader, String path) {
try (InputStream is = openStream(classLoader, path)) {
// strips away comments
return SQL_SCRIPT_PARSER.parse(new ANTLRInputStream(is));
} catch (IOException e) {
throw new RuntimeException("Unable to read classpath resource at " + path, e);
}
}
private static InputStream openStream(ClassLoader classLoader, String path) {
InputStream is = classLoader.getResourceAsStream(path);
if (is == null) {
throw new IllegalArgumentException("Cannot find classpath resource at " + path);
}
return is;
}
private static ClassLoader selectClassLoader() {
return Optional.ofNullable(Thread.currentThread().getContextClassLoader())
.orElseGet(ClasspathSqlLocator.class::getClassLoader);
}
}