SGF-392 - Add support for OQL Query statement extensions in Repository Query methods via Annotations.
Implementation and unit test coverage complete.
This commit is contained in:
@@ -26,8 +26,7 @@ import org.springframework.data.util.TypeInformation;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class GemfireMappingContext extends
|
||||
AbstractMappingContext<GemfirePersistentEntity<?>, GemfirePersistentProperty> {
|
||||
public class GemfireMappingContext extends AbstractMappingContext<GemfirePersistentEntity<?>, GemfirePersistentProperty> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -47,4 +46,5 @@ public class GemfireMappingContext extends
|
||||
GemfirePersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
|
||||
return new GemfirePersistentProperty(field, descriptor, owner, simpleTypeHolder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -75,8 +75,9 @@ public class Regions implements Iterable<Region<?, ?>> {
|
||||
Assert.notNull(type);
|
||||
|
||||
GemfirePersistentEntity<?> entity = context.getPersistentEntity(type);
|
||||
String regionName = (entity != null ? entity.getRegionName() : type.getSimpleName());
|
||||
|
||||
return (Region<?, T>) (entity == null ? regions.get(type.getSimpleName()) : regions.get(entity.getRegionName()));
|
||||
return (Region<?, T>) regions.get(regionName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@@ -33,9 +34,11 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Gemfire specific {@link QueryMethod}.
|
||||
* GemFire specific {@link QueryMethod}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class GemfireQueryMethod extends QueryMethod {
|
||||
@@ -76,8 +79,8 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
private void assertNonPagingQueryMethod(final Method method) {
|
||||
for (Class<?> type : method.getParameterTypes()) {
|
||||
if (Pageable.class.isAssignableFrom(type)) {
|
||||
throw new IllegalStateException("Pagination is not supported by GemFire Repositories! Offending method: "
|
||||
+ method.toString());
|
||||
throw new IllegalStateException(String.format("Pagination is not supported by GemFire Repositories!"
|
||||
+ " Offending method: %1$s", method.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,12 +34,12 @@ class QueryBuilder {
|
||||
private final String query;
|
||||
|
||||
public QueryBuilder(String source) {
|
||||
Assert.hasText(source, "The OQL Query string must be specified.");
|
||||
Assert.hasText(source, "The OQL Query must be specified");
|
||||
this.query = source;
|
||||
}
|
||||
|
||||
public QueryBuilder(GemfirePersistentEntity<?> entity, PartTree tree) {
|
||||
this(String.format(tree.isDistinct() ? "SELECT DISTINCT * FROM /%1$s %2$s" : "SELECT * FROM /%1$s %2$s",
|
||||
this(String.format("SELECT%1$s * FROM /%2$s %3$s", (tree.isDistinct() ? " DISTINCT" : ""),
|
||||
entity.getRegionName(), DEFAULT_ALIAS));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
@@ -36,6 +37,19 @@ import com.gemstone.gemfire.cache.Region;
|
||||
*/
|
||||
public class QueryString {
|
||||
|
||||
protected static final Pattern HINT_PATTERN = Pattern.compile("<HINT '\\w+'(, '\\w+')*>");
|
||||
protected static final Pattern IMPORT_PATTERN = Pattern.compile("IMPORT .+;");
|
||||
protected static final Pattern LIMIT_PATTERN = Pattern.compile("LIMIT \\d+");
|
||||
protected static final Pattern TRACE_PATTERN = Pattern.compile("<TRACE>");
|
||||
|
||||
// OQL Query Templates
|
||||
private static final String HINTS_QUERY_TEMPLATE = "<HINT %1$s> %2$s";
|
||||
private static final String IMPORT_QUERY_TEMPLATE = "IMPORT %1$s; %2$s";
|
||||
private static final String LIMIT_QUERY_TEMPLATE = "%1$s LIMIT %2$d";
|
||||
private static final String SELECT_QUERY_TEMPLATE = "SELECT %1$s FROM /%2$s";
|
||||
private static final String TRACE_QUERY_TEMPLATE = "<TRACE> %1$s";
|
||||
|
||||
// Query Regular Expression Patterns
|
||||
private static final String IN_PATTERN = "(?<=IN (SET|LIST) )\\$\\d";
|
||||
private static final String IN_PARAMETER_PATTERN = "(?<=IN (SET|LIST) \\$)\\d";
|
||||
private static final String REGION_PATTERN = "\\/(\\/?\\w)+";
|
||||
@@ -69,8 +83,7 @@ public class QueryString {
|
||||
* @param isCountQuery indicates if this is a count query
|
||||
*/
|
||||
public QueryString(Class<?> domainClass, boolean isCountQuery) {
|
||||
this(String.format(isCountQuery ? "SELECT count(*) FROM /%s" : "SELECT * FROM /%s",
|
||||
domainClass.getSimpleName()));
|
||||
this(String.format(SELECT_QUERY_TEMPLATE, (isCountQuery ? "count(*)" : "*"), domainClass.getSimpleName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,6 +159,34 @@ public class QueryString {
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryString withHints(String... hints) {
|
||||
if (!ObjectUtils.isEmpty(hints)) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (String hint : hints) {
|
||||
builder.append(builder.length() > 0 ? ", " : "");
|
||||
builder.append(String.format("'%1$s'", hint));
|
||||
}
|
||||
|
||||
return new QueryString(String.format(HINTS_QUERY_TEMPLATE, builder.toString(), query));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryString withImport(String importExpression) {
|
||||
return (StringUtils.hasText(importExpression) ? new QueryString(
|
||||
String.format(IMPORT_QUERY_TEMPLATE, importExpression, query)) : this);
|
||||
}
|
||||
|
||||
public QueryString withLimit(Integer limit) {
|
||||
return (limit != null ? new QueryString(String.format(LIMIT_QUERY_TEMPLATE, query, limit)) : this);
|
||||
}
|
||||
|
||||
public QueryString withTrace() {
|
||||
return new QueryString(String.format(TRACE_QUERY_TEMPLATE, query));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
|
||||
@@ -109,16 +109,18 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
public Object execute(Object[] parameters) {
|
||||
QueryMethod localQueryMethod = getQueryMethod();
|
||||
|
||||
ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(
|
||||
localQueryMethod.getParameters(), parameters);
|
||||
|
||||
QueryString query = (isUserDefinedQuery() ? this.query : this.query.forRegion(
|
||||
localQueryMethod.getEntityInformation().getJavaType(), template.getRegion()));
|
||||
|
||||
ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(
|
||||
localQueryMethod.getParameters(), parameters);
|
||||
|
||||
for (Integer index : query.getInParameterIndexes()) {
|
||||
query = query.bindIn(toCollection(parameterAccessor.getBindableValue(index - 1)));
|
||||
}
|
||||
|
||||
query = applyQueryAnnotationExtensions(localQueryMethod, query);
|
||||
|
||||
Collection<?> result = toCollection(template.find(query.toString(), parameters));
|
||||
|
||||
if (localQueryMethod.isCollectionQuery()) {
|
||||
@@ -143,6 +145,29 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
}
|
||||
}
|
||||
|
||||
QueryString applyQueryAnnotationExtensions(final QueryMethod queryMethod, final QueryString queryString) {
|
||||
QueryString resolvedQueryString = queryString;
|
||||
|
||||
if (queryMethod instanceof GemfireQueryMethod) {
|
||||
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
|
||||
String query = queryString.toString().toUpperCase();
|
||||
|
||||
if (gemfireQueryMethod.hasImport() && !QueryString.IMPORT_PATTERN.matcher(query).find()) {
|
||||
resolvedQueryString = resolvedQueryString.withImport(gemfireQueryMethod.getImport());
|
||||
}
|
||||
if (gemfireQueryMethod.hasHint() && !QueryString.HINT_PATTERN.matcher(query).find()) {
|
||||
resolvedQueryString = resolvedQueryString.withHints(gemfireQueryMethod.getHints());
|
||||
}
|
||||
if (gemfireQueryMethod.hasLimit() && !QueryString.LIMIT_PATTERN.matcher(query).find()) {
|
||||
resolvedQueryString = resolvedQueryString.withLimit(gemfireQueryMethod.getLimit());
|
||||
}
|
||||
if (gemfireQueryMethod.hasTrace() && !QueryString.TRACE_PATTERN.matcher(query).find()) {
|
||||
resolvedQueryString = resolvedQueryString.withTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedQueryString;
|
||||
}
|
||||
|
||||
boolean isSingleResultNonEntityQuery(QueryMethod method, Collection<?> result) {
|
||||
return (!method.isCollectionQuery() && method.getReturnedObjectType() != null
|
||||
|
||||
@@ -64,7 +64,7 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
public GemfireRepositoryFactory(Iterable<Region<?, ?>> regions, MappingContext<? extends GemfirePersistentEntity<?>,
|
||||
GemfirePersistentProperty> context) {
|
||||
Assert.notNull(regions);
|
||||
this.context = context == null ? new GemfireMappingContext() : context;
|
||||
this.context = (context != null ? context : new GemfireMappingContext());
|
||||
this.regions = new Regions(regions, this.context);
|
||||
}
|
||||
|
||||
@@ -108,8 +108,8 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
Region<?, ?> region = regions.getRegion(regionName);
|
||||
|
||||
if (region == null) {
|
||||
throw new IllegalStateException(String.format("No region '%s' found for domain class %s!"
|
||||
+ " Make sure you have configured a Gemfire region of that name in your application context!",
|
||||
throw new IllegalStateException(String.format("No Region '%1$s' found for domain class %2$s!"
|
||||
+ " Make sure you have configured a GemFire Region of that name in your application context!",
|
||||
regionName, metadata.getDomainType()));
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
if (regionKeyType != null && entity.getIdProperty() != null) {
|
||||
Assert.isTrue(regionKeyType.isAssignableFrom(entityIdType), String.format(
|
||||
"The region referenced only supports keys of type %s but the entity to be stored has an id of type %s!",
|
||||
"The Region referenced only supports keys of type %1$s but the entity to be stored has an id of type %2$s!",
|
||||
regionKeyType, entityIdType));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user