/* * Copyright 2012 the original author or authors. * * 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.springframework.data.gemfire.repository.query; import java.util.Collection; import java.util.Collections; import org.apache.geode.cache.query.SelectResults; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.gemfire.GemfireTemplate; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethod; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * {@link GemfireRepositoryQuery} using plain {@link String} based OQL queries. * <p> * @author Oliver Gierke * @author David Turanski * @author John Blum */ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { private static final String INVALID_QUERY = "Paging and modifying queries are not supported"; private boolean userDefinedQuery = false; private final GemfireTemplate template; private final QueryString query; /* * (non-Javadoc) * Constructor used for testing purposes only! */ StringBasedGemfireRepositoryQuery() { query = null; template = null; } /** * Creates a new {@link StringBasedGemfireRepositoryQuery} using the given {@link GemfireQueryMethod} and * {@link GemfireTemplate}. The actual query {@link String} will be looked up from the query method. * * @param queryMethod must not be {@literal null}. * @param template must not be {@literal null}. */ public StringBasedGemfireRepositoryQuery(GemfireQueryMethod queryMethod, GemfireTemplate template) { this(queryMethod.getAnnotatedQuery(), queryMethod, template); } /** * Creates a new {@link StringBasedGemfireRepositoryQuery} using the given query {@link String}, * {@link GemfireQueryMethod} and {@link GemfireTemplate}. * * @param query will fall back to the query annotated to the given {@link GemfireQueryMethod} if {@literal null}. * @param queryMethod must not be {@literal null}. * @param template must not be {@literal null}. */ public StringBasedGemfireRepositoryQuery(String query, GemfireQueryMethod queryMethod, GemfireTemplate template) { super(queryMethod); Assert.notNull(template, "GemfireTemplate must not be null"); this.userDefinedQuery |= !StringUtils.hasText(query); this.query = new QueryString(StringUtils.hasText(query) ? query : queryMethod.getAnnotatedQuery()); this.template = template; if (queryMethod.isModifyingQuery() || queryMethod.isPageQuery()) { throw new IllegalStateException(INVALID_QUERY); } } /* * (non-Javadoc) */ public StringBasedGemfireRepositoryQuery asUserDefinedQuery() { this.userDefinedQuery = true; return this; } /* * (non-Javadoc) */ public boolean isUserDefinedQuery() { return userDefinedQuery; } /* * (non-Javadoc) * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) */ @Override public Object execute(Object[] parameters) { QueryMethod localQueryMethod = getQueryMethod(); 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()) { return result; } else if (localQueryMethod.isQueryForEntity()) { if (result.isEmpty()) { return null; } else if (result.size() == 1) { return result.iterator().next(); } else { throw new IncorrectResultSizeDataAccessException(1, result.size()); } } else if (isSingleResultNonEntityQuery(localQueryMethod, result)) { return result.iterator().next(); } else { throw new IllegalStateException("Unsupported query: " + query.toString()); } } QueryString applyQueryAnnotationExtensions(QueryMethod queryMethod, 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 && !Void.TYPE.equals(method.getReturnedObjectType()) && result != null && result.size() == 1); } /** * Returns the given object as a Collection. Collections will be returned as is, Arrays will be converted into a * Collection and all other objects will be wrapped into a single-element Collection. * * @param source the resulting object from the GemFire Query. * @return the querying resulting object as a Collection. * @see java.util.Arrays#asList(Object[]) * @see java.util.Collection * @see org.springframework.util.CollectionUtils#arrayToList(Object) * @see org.apache.geode.cache.query.SelectResults */ Collection<?> toCollection(final Object source) { if (source instanceof SelectResults) { return ((SelectResults) source).asList(); } if (source instanceof Collection) { return (Collection<?>) source; } if (source == null) { return Collections.emptyList(); } return (source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singletonList(source)); } }