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:
John Blum
2015-08-11 23:08:25 -07:00
parent c022f2c005
commit 23d05aeaee
12 changed files with 500 additions and 56 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
/**

View File

@@ -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()));
}
}
}

View File

@@ -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));
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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));
}