Repository: ignite Updated Branches: refs/heads/ignite-1192 2ab0999d4 -> 423709fb1
IGNITE-1192: finished implementation, working on the tests Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/423709fb Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/423709fb Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/423709fb Branch: refs/heads/ignite-1192 Commit: 423709fb131518d499619c0060bf8a4128b86699 Parents: 2ab0999 Author: Denis Magda <[email protected]> Authored: Mon Apr 10 18:49:51 2017 -0700 Committer: Denis Magda <[email protected]> Committed: Mon Apr 10 18:49:51 2017 -0700 ---------------------------------------------------------------------- modules/spring-data/pom.xml | 15 +- .../springdata/IgniteKeyValueAdapter.java | 2 +- .../ignite/springdata/repository/Query.java | 37 +++ .../config/EnableIgniteRepositories.java | 12 +- .../IgniteRepositoryConfigurationExtension.java | 14 + .../repository/config/RepositoryConfig.java | 39 +++ .../repository/query/IgniteQuery.java | 83 +++++ .../repository/query/IgniteQueryGenerator.java | 243 +++++++++++++++ .../repository/query/IgniteRepositoryQuery.java | 306 +++++++++++++++++++ .../support/IgniteRepositoryFactory.java | 176 +++++++++++ .../support/IgniteRepositoryFactoryBean.java | 55 ++++ .../springdata/IgniteSpringDataSelfTest.java | 286 +++++++++++++++++ .../springdata/IgniteSpringDataTestSuite.java | 38 +++ .../misc/ApplicationConfiguration.java | 59 ++++ .../ignite/springdata/misc/FirstRepository.java | 92 ++++++ .../apache/ignite/springdata/misc/Person.java | 75 +++++ .../springdata/misc/SecondRepository.java | 40 +++ 17 files changed, 1563 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/pom.xml ---------------------------------------------------------------------- diff --git a/modules/spring-data/pom.xml b/modules/spring-data/pom.xml index 6cce90a..5954760 100644 --- a/modules/spring-data/pom.xml +++ b/modules/spring-data/pom.xml @@ -42,9 +42,22 @@ </dependency> <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-indexing</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-log4j</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> - <version>${spring.version}</version> + <version>4.3.7.RELEASE</version> </dependency> <dependency> http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/IgniteKeyValueAdapter.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/IgniteKeyValueAdapter.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/IgniteKeyValueAdapter.java index bd299a9..ff5b727 100644 --- a/modules/spring-data/src/main/java/org/apache/ignite/springdata/IgniteKeyValueAdapter.java +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/IgniteKeyValueAdapter.java @@ -185,7 +185,7 @@ public class IgniteKeyValueAdapter extends AbstractKeyValueAdapter { if (!keyspace.getClass().equals(String.class)) throw new IgniteException("Keyspace must be of String type"); - return ignite.cache((String)keyspace); + return ignite.getOrCreateCache((String)keyspace); } /** http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/Query.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/Query.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/Query.java new file mode 100644 index 0000000..08b7266 --- /dev/null +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/Query.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to provide a user defined SQL query for a method. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Query { + /** + * SQL query text string. + */ + String value() default ""; +} http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/EnableIgniteRepositories.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/EnableIgniteRepositories.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/EnableIgniteRepositories.java index 0705e88..1b18e4d 100644 --- a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/EnableIgniteRepositories.java +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/EnableIgniteRepositories.java @@ -22,6 +22,8 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.apache.ignite.springdata.repository.support.IgniteRepositoryFactoryBean; +import org.apache.ignite.springdata.repository.support.SimpleIgniteRepository; import org.springframework.beans.factory.FactoryBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan.Filter; @@ -39,15 +41,12 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key; /** * Annotation to activate Apache Ignite repositories. If no base package is configured through either {@link #value()}, * {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class. - * - * TODO: replace QueryCreatorType with Ignite version of the querying engine. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(IgniteRepositoriesRegistar.class) -@QueryCreatorType(value = SpelQueryCreator.class, repositoryQueryType = CachingKeyValuePartTreeQuery.class) public @interface EnableIgniteRepositories { /** * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: @@ -106,20 +105,19 @@ public @interface EnableIgniteRepositories { Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND; /** - * TODO: replace with Ignite factory bean. * Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to - * {@link KeyValueRepositoryFactoryBean}. + * {@link IgniteRepositoryFactoryBean}. * * @return */ - Class<?> repositoryFactoryBeanClass() default KeyValueRepositoryFactoryBean.class; + Class<?> repositoryFactoryBeanClass() default IgniteRepositoryFactoryBean.class; /** * Configure the repository base class to be used to create repository proxies for this particular configuration. * * @return */ - Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class; + Class<?> repositoryBaseClass() default SimpleIgniteRepository.class; /** * Configures the name of the {@link KeyValueOperations} bean to be used with the repositories detected. http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/IgniteRepositoryConfigurationExtension.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/IgniteRepositoryConfigurationExtension.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/IgniteRepositoryConfigurationExtension.java index 30f9385..9c2965c 100644 --- a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/IgniteRepositoryConfigurationExtension.java +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/IgniteRepositoryConfigurationExtension.java @@ -16,6 +16,10 @@ */ package org.apache.ignite.springdata.repository.config; +import java.util.Collection; +import java.util.Collections; +import org.apache.ignite.springdata.repository.IgniteRepository; +import org.apache.ignite.springdata.repository.support.IgniteRepositoryFactoryBean; import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension; import org.springframework.data.repository.config.RepositoryConfigurationExtension; @@ -37,4 +41,14 @@ public class IgniteRepositoryConfigurationExtension extends KeyValueRepositoryCo @Override protected String getDefaultKeyValueTemplateRef() { return "igniteKeyValueTemplate"; } + + /** {@inheritDoc} */ + @Override public String getRepositoryFactoryClassName() { + return IgniteRepositoryFactoryBean.class.getName(); + } + + /** {@inheritDoc} */ + @Override protected Collection<Class<?>> getIdentifyingTypes() { + return Collections.<Class<?>>singleton(IgniteRepository.class); + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/RepositoryConfig.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/RepositoryConfig.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/RepositoryConfig.java new file mode 100644 index 0000000..b30ef2e --- /dev/null +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/config/RepositoryConfig.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.repository.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The annotation can be used to pass Ignite specific parameters to a bound repository. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface RepositoryConfig { + /** + * @return A name of a distributed Apache Ignite cache the repository will be mapped to. + */ + String cacheName() default ""; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteQuery.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteQuery.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteQuery.java new file mode 100644 index 0000000..f630ca0 --- /dev/null +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteQuery.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.repository.query; + +/** + * Ignite query helper class. For internal use only. + */ +public class IgniteQuery { + /** */ + enum Option { + /** Query will be used with Sort object. */ + SORTING, + + /** Query will be used with Pageable object. */ + PAGINATION, + + /** No advanced option. */ + NONE + } + + /** Sql query text string. */ + private final String sql; + + /** */ + private final boolean isFieldQuery; + + /** Type of option. */ + private final Option option; + + /** + * @param sql Sql. + * @param isFieldQuery Is field query. + * @param option Option. + */ + public IgniteQuery(String sql, boolean isFieldQuery, Option option) { + this.sql = sql; + this.isFieldQuery = isFieldQuery; + this.option = option; + } + + /** + * Text string of the query. + * + * @return SQL query text string. + */ + public String sql() { + return sql; + } + + /** + * Returns {@code true} if it's Ignite SQL fields query, {@code false} otherwise. + * + * @return {@code true} if it's Ignite SQL fields query, {@code false} otherwise. + */ + public boolean isFieldQuery() { + return isFieldQuery; + } + + /** + * Advanced querying option. + * + * @return querying option. + */ + public Option options() { + return option; + } +} + http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteQueryGenerator.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteQueryGenerator.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteQueryGenerator.java new file mode 100644 index 0000000..2db2789 --- /dev/null +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteQueryGenerator.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.repository.query; + +import java.lang.reflect.Method; +import org.jetbrains.annotations.NotNull; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.PartTree; + +/** + * Ignite query generator for Spring Data framework. + */ +public class IgniteQueryGenerator { + + /** + * @param mtd Method. + * @param metadata Metadata. + */ + @NotNull public static IgniteQuery generateSql(Method mtd, RepositoryMetadata metadata) { + PartTree parts = new PartTree(mtd.getName(), metadata.getDomainType()); + + StringBuilder sql = new StringBuilder(); + + if (parts.isDelete()) + throw new UnsupportedOperationException("DELETE clause is not supported now."); + else { + sql.append("SELECT "); + + if (parts.isDistinct()) + throw new UnsupportedOperationException("DISTINCT clause in not supported."); + + if (parts.isCountProjection()) + sql.append("COUNT(1) "); + else + sql.append(" * "); + } + + sql.append("FROM ").append(metadata.getDomainType().getSimpleName()); + + if (parts.iterator().hasNext()) { + sql.append(" WHERE "); + + for (PartTree.OrPart orPart : parts) { + sql.append("("); + for (Part part : orPart) { + handleQueryPart(sql, part); + sql.append(" AND "); + } + + sql.delete(sql.length() - 5, sql.length()); + + sql.append(") OR "); + } + + sql.delete(sql.length() - 4, sql.length()); + } + + addSorting(sql, parts.getSort()); + + if (parts.isLimiting()) { + sql.append(" LIMIT "); + sql.append(parts.getMaxResults().intValue()); + } + + return new IgniteQuery(sql.toString(), parts.isCountProjection(), getOptions(mtd)); + } + + /** + * Add a dynamic part of query for the sorting support. + * + * @param sql SQL text string. + * @param sort Sort method. + */ + public static StringBuilder addSorting(StringBuilder sql, Sort sort) { + if (sort != null) { + sql.append(" ORDER BY "); + + for (Sort.Order order : sort) { + sql.append(order.getProperty()).append(" ").append(order.getDirection()); + + if (order.getNullHandling() != Sort.NullHandling.NATIVE) { + sql.append(" ").append("NULL "); + switch (order.getNullHandling()) { + case NULLS_FIRST: + sql.append("FIRST"); + break; + case NULLS_LAST: + sql.append("LAST"); + break; + } + } + sql.append(", "); + } + + sql.delete(sql.length() - 2, sql.length()); + } + + return sql; + } + + /** + * Add a dynamic part of a query for the pagination support. + * + * @param sql + * @param pageable + * @return + */ + public static StringBuilder addPaging(StringBuilder sql, Pageable pageable) { + if (pageable.getSort() != null) + addSorting(sql, pageable.getSort()); + + sql.append(" LIMIT ").append(pageable.getPageSize()).append(" OFFSET ").append(pageable.getOffset()); + + return sql; + } + + /** + * Determines whether query is dynamic or not (by list of method parameters) + * + * @param mtd + * @return type of options + */ + public static IgniteQuery.Option getOptions(Method mtd) { + IgniteQuery.Option option; + + Class<?>[] types = mtd.getParameterTypes(); + Class<?> type = types[types.length - 1]; + + if (Sort.class.isAssignableFrom(type)) + option = IgniteQuery.Option.SORTING; + else if (Pageable.class.isAssignableFrom(type)) + option = IgniteQuery.Option.PAGINATION; + else + option = IgniteQuery.Option.NONE; + + for (int i = 0; i < types.length - 1; i++) { + Class<?> tp = types[i]; + if (tp == Sort.class || tp == Pageable.class) + throw new AssertionError("Sort and Pageable parameters are allowed only in the last position"); + } + + return option; + } + + /** + * Transform part to sql expression + */ + private static void handleQueryPart(StringBuilder sql, Part part) { + sql.append("("); + + sql.append(part.getProperty()); + + switch (part.getType()) { + case SIMPLE_PROPERTY: + sql.append("=?"); + break; + case NEGATING_SIMPLE_PROPERTY: + sql.append("<>?"); + break; + case GREATER_THAN: + sql.append(">?"); + break; + case GREATER_THAN_EQUAL: + sql.append(">=?"); + break; + case LESS_THAN: + sql.append("<?"); + break; + case LESS_THAN_EQUAL: + sql.append("<=?"); + break; + case IS_NOT_NULL: + sql.append(" IS NOT NULL"); + break; + case IS_NULL: + sql.append(" IS NULL"); + break; + case BETWEEN: + sql.append(" BETWEEN ? AND ?"); + break; + case FALSE: + sql.append(" = FALSE"); + break; + case TRUE: + sql.append(" = TRUE"); + break; + case CONTAINING: + sql.append(" LIKE '%' || ? || '%'"); + break; + case NOT_CONTAINING: + sql.append(" NOT LIKE '%' || ? || '%'"); + break; + case LIKE: + sql.append(" LIKE '%' || ? || '%'"); + break; + case NOT_LIKE: + sql.append(" NOT LIKE '%' || ? || '%'"); + break; + case STARTING_WITH: + sql.append(" LIKE ? || '%'"); + break; + case ENDING_WITH: + sql.append(" LIKE '%' || ?"); + break; + case IN: + sql.append(" IN ?"); + break; + case NOT_IN: + sql.append(" NOT IN ?"); + break; + case REGEX: + sql.append(" REGEXP ?"); + break; + case NEAR: + case AFTER: + case BEFORE: + case EXISTS: + default: + throw new UnsupportedOperationException(part.getType() + " is not supported!"); + } + + sql.append(")"); + } +} + http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteRepositoryQuery.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteRepositoryQuery.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteRepositoryQuery.java new file mode 100644 index 0000000..c6e171f --- /dev/null +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/query/IgniteRepositoryQuery.java @@ -0,0 +1,306 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.repository.query; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import javax.cache.Cache; +import org.apache.ignite.IgniteCache; +import org.apache.ignite.cache.query.Query; +import org.apache.ignite.cache.query.QueryCursor; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.cache.query.SqlQuery; +import org.apache.ignite.internal.processors.cache.CacheEntryImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.data.domain.Sort; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; + +/** + * Ignite SQL query implementation. + */ +@SuppressWarnings("unchecked") +public class IgniteRepositoryQuery implements RepositoryQuery { + /** Defines the way how to process query result */ + private enum ReturnStrategy { + /** Need to return only one value. */ + ONE_VALUE, + + /** Need to return one cache entry */ + CACHE_ENTRY, + + /** Need to return list of cache entries */ + LIST_OF_CACHE_ENTRIES, + + /** Need to return list of values */ + LIST_OF_VALUES, + + /** Need to return list of lists */ + LIST_OF_LISTS, + + /** Need to return slice */ + SLICE_OF_VALUES, + + /** Slice of cache entries. */ + SLICE_OF_CACHE_ENTRIES, + + /** Slice of lists. */ + SLICE_OF_LISTS + } + + /** Type. */ + private final Class<?> type; + /** Sql. */ + private final IgniteQuery qry; + /** Cache. */ + private final IgniteCache cache; + + /** Method. */ + private final Method mtd; + /** Metadata. */ + private final RepositoryMetadata metadata; + /** Factory. */ + private final ProjectionFactory factory; + + /** Return strategy. */ + private final ReturnStrategy returnStgy; + + /** + * @param metadata Metadata. + * @param qry Query. + * @param mtd Method. + * @param factory Factory. + * @param cache Cache. + */ + public IgniteRepositoryQuery(RepositoryMetadata metadata, IgniteQuery qry, + Method mtd, ProjectionFactory factory, IgniteCache cache) { + type = metadata.getDomainType(); + this.qry = qry; + this.cache = cache; + + this.metadata = metadata; + this.mtd = mtd; + this.factory = factory; + + returnStgy = calcReturnType(mtd, qry.isFieldQuery()); + } + + /** {@inheritDoc} */ + @Override public Object execute(Object[] prmtrs) { + Query qry = prepareQuery(prmtrs); + + QueryCursor qryCursor = cache.query(qry); + + return transformQueryCursor(prmtrs, qryCursor); + } + + /** {@inheritDoc} */ + @Override public QueryMethod getQueryMethod() { + return new QueryMethod(mtd, metadata, factory); + } + + /** + * @param mtd Method. + * @param isFieldQry Is field query. + */ + private ReturnStrategy calcReturnType(Method mtd, boolean isFieldQry) { + Class<?> returnType = mtd.getReturnType(); + + if (returnType.isAssignableFrom(ArrayList.class)) { + if (isFieldQry) { + if (hasAssignableGenericReturnTypeFrom(ArrayList.class, mtd)) + return ReturnStrategy.LIST_OF_LISTS; + } + else if (hasAssignableGenericReturnTypeFrom(Cache.Entry.class, mtd)) + return ReturnStrategy.LIST_OF_CACHE_ENTRIES; + + return ReturnStrategy.LIST_OF_VALUES; + } + else if (returnType == Slice.class) { + if (isFieldQry) { + if (hasAssignableGenericReturnTypeFrom(ArrayList.class, mtd)) + return ReturnStrategy.SLICE_OF_LISTS; + } + else if (hasAssignableGenericReturnTypeFrom(Cache.Entry.class, mtd)) + return ReturnStrategy.SLICE_OF_CACHE_ENTRIES; + + return ReturnStrategy.SLICE_OF_VALUES; + } + else if (Cache.Entry.class.isAssignableFrom(returnType)) + return ReturnStrategy.CACHE_ENTRY; + else + return ReturnStrategy.ONE_VALUE; + } + + /** + * @param cls Class 1. + * @param mtd Method. + */ + private boolean hasAssignableGenericReturnTypeFrom(Class<?> cls, Method mtd) { + Type[] actualTypeArguments = ((ParameterizedType)mtd.getGenericReturnType()).getActualTypeArguments(); + + if (actualTypeArguments.length == 0) + return false; + + if (actualTypeArguments[0] instanceof ParameterizedType) { + ParameterizedType type = (ParameterizedType)actualTypeArguments[0]; + + Class<?> type1 = (Class)type.getRawType(); + + return type1.isAssignableFrom(cls); + } + + if (actualTypeArguments[0] instanceof Class) { + Class typeArg = (Class)actualTypeArguments[0]; + + return typeArg.isAssignableFrom(cls); + } + + return false; + } + + /** + * @param prmtrs Prmtrs. + * @param qryCursor Query cursor. + */ + @Nullable private Object transformQueryCursor(Object[] prmtrs, QueryCursor qryCursor) { + if (this.qry.isFieldQuery()) { + Iterable<ArrayList> qryIter = (Iterable<ArrayList>)qryCursor; + + switch (returnStgy) { + case LIST_OF_VALUES: + ArrayList list = new ArrayList(); + + for (ArrayList entry : qryIter) + list.add(entry.get(0)); + + return list; + case ONE_VALUE: + Iterator<ArrayList> iter = qryIter.iterator(); + + if (iter.hasNext()) + return iter.next().get(0); + + return null; + case SLICE_OF_VALUES: + ArrayList content = new ArrayList(); + + for (ArrayList entry : qryIter) + content.add(entry.get(0)); + + return new SliceImpl(content, (Pageable)prmtrs[prmtrs.length - 1], true); + case SLICE_OF_LISTS: + return new SliceImpl(qryCursor.getAll(), (Pageable)prmtrs[prmtrs.length - 1], true); + case LIST_OF_LISTS: + return qryCursor.getAll(); + default: + throw new IllegalStateException(); + } + } + else { + Iterable<CacheEntryImpl> qryIter = (Iterable<CacheEntryImpl>)qryCursor; + + switch (returnStgy) { + case LIST_OF_VALUES: + ArrayList list = new ArrayList(); + + for (CacheEntryImpl entry : qryIter) + list.add(entry.getValue()); + + return list; + case ONE_VALUE: + Iterator<CacheEntryImpl> iter1 = qryIter.iterator(); + + if (iter1.hasNext()) + return iter1.next().getValue(); + + return null; + case CACHE_ENTRY: + Iterator<CacheEntryImpl> iter2 = qryIter.iterator(); + + if (iter2.hasNext()) + return iter2.next(); + + return null; + case SLICE_OF_VALUES: + ArrayList content = new ArrayList(); + + for (CacheEntryImpl entry : qryIter) + content.add(entry.getValue()); + + return new SliceImpl(content, (Pageable)prmtrs[prmtrs.length - 1], true); + case SLICE_OF_CACHE_ENTRIES: + return new SliceImpl(qryCursor.getAll(), (Pageable)prmtrs[prmtrs.length - 1], true); + case LIST_OF_CACHE_ENTRIES: + return qryCursor.getAll(); + default: + throw new IllegalStateException(); + } + } + } + + /** + * @param prmtrs Prmtrs. + * @return prepared query for execution + */ + @NotNull private Query prepareQuery(Object[] prmtrs) { + Object[] parameters = prmtrs; + String sql = qry.sql(); + + Query query; + + switch (qry.options()) { + case SORTING: + sql = IgniteQueryGenerator.addSorting(new StringBuilder(sql), + (Sort)parameters[parameters.length - 1]).toString(); + parameters = Arrays.copyOfRange(parameters, 0, parameters.length - 1); + break; + case PAGINATION: + sql = IgniteQueryGenerator.addPaging(new StringBuilder(sql), + (Pageable)parameters[parameters.length - 1]).toString(); + parameters = Arrays.copyOfRange(parameters, 0, parameters.length - 1); + break; + } + + if (qry.isFieldQuery()) { + SqlFieldsQuery sqlFieldsQry = new SqlFieldsQuery(sql); + sqlFieldsQry.setArgs(parameters); + + query = sqlFieldsQry; + } + else { + SqlQuery sqlQry = new SqlQuery(type, sql); + sqlQry.setArgs(parameters); + + query = sqlQry; + } + + return query; + } +} + http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/support/IgniteRepositoryFactory.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/support/IgniteRepositoryFactory.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/support/IgniteRepositoryFactory.java new file mode 100644 index 0000000..006bd5a --- /dev/null +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/support/IgniteRepositoryFactory.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.repository.support; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteException; +import org.apache.ignite.springdata.IgniteKeyValueAdapter; +import org.apache.ignite.springdata.repository.IgniteRepository; +import org.apache.ignite.springdata.repository.Query; +import org.apache.ignite.springdata.repository.config.RepositoryConfig; +import org.apache.ignite.springdata.repository.query.IgniteQuery; +import org.apache.ignite.springdata.repository.query.IgniteQueryGenerator; +import org.apache.ignite.springdata.repository.query.IgniteRepositoryQuery; +import org.springframework.data.keyvalue.core.KeyValueAdapter; +import org.springframework.data.keyvalue.core.KeyValueOperations; +import org.springframework.data.keyvalue.core.KeyValueTemplate; +import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.EvaluationContextProvider; +import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.data.util.ReflectionUtils; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Crucial for spring-data functionality class. Create proxies for repositories. + */ +public class IgniteRepositoryFactory extends KeyValueRepositoryFactory { + /** Ignite instance */ + private Ignite ignite; + + /** Mapping of a repository to a cache. */ + private final Map<Class<?>, String> repoToCache = new HashMap<>(); + + public IgniteRepositoryFactory(KeyValueOperations keyValueOperations) { + super(keyValueOperations); + + initRepositoryFactory(keyValueOperations); + } + + public IgniteRepositoryFactory(KeyValueOperations keyValueOperations, + Class<? extends AbstractQueryCreator<?, ?>> queryCreator) { + super(keyValueOperations, queryCreator); + + initRepositoryFactory(keyValueOperations); + } + + public IgniteRepositoryFactory(KeyValueOperations keyValueOperations, + Class<? extends AbstractQueryCreator<?, ?>> queryCreator, + Class<? extends RepositoryQuery> repositoryQueryType) { + super(keyValueOperations, queryCreator, repositoryQueryType); + + initRepositoryFactory(keyValueOperations); + } + + private void initRepositoryFactory(KeyValueOperations keyValueOperations) { + try { + Field field = ReflectionUtils.findField(KeyValueTemplate.class, + new org.springframework.util.ReflectionUtils.FieldFilter() { + @Override public boolean matches(Field field) { + return field.getName().equals("adapter"); + } + }); + + field.setAccessible(true); + + IgniteKeyValueAdapter adapter = (IgniteKeyValueAdapter)field.get(keyValueOperations); + + field = ReflectionUtils.findField(IgniteKeyValueAdapter.class, + new org.springframework.util.ReflectionUtils.FieldFilter() { + @Override public boolean matches(Field field) { + return field.getName().equals("ignite"); + } + }); + + field.setAccessible(true); + + ignite = (Ignite)field.get(adapter); + + if (ignite == null) + throw new IgniteException("Failed to initialize IgniteRepositoryFactory properly: Ignite instance is " + + "not set in IgniteKeyValueAdapter"); + } + catch (Exception e) { + throw new IgniteException("Failed to initialize IgniteRepositoryFactory properly.", e); + } + } + + /** {@inheritDoc} */ + @Override protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { + return SimpleIgniteRepository.class; + } + + /** {@inheritDoc} */ + @Override protected RepositoryMetadata getRepositoryMetadata(Class<?> repoItf) { + Assert.notNull(repoItf, "Repository interface must be set."); + Assert.isAssignable(IgniteRepository.class, repoItf, "Repository must implement IgniteRepository interface."); + + RepositoryConfig annotation = repoItf.getAnnotation(RepositoryConfig.class); + + Assert.notNull(annotation, "Set a name of an Apache Ignite cache using @RepositoryConfig annotation to map " + + "this repository to the underlying cache."); + + Assert.hasText(annotation.cacheName(), "Set a name of an Apache Ignite cache using @RepositoryConfig " + + "annotation to map this repository to the underlying cache."); + + repoToCache.put(repoItf, annotation.cacheName()); + + return super.getRepositoryMetadata(repoItf); + } + + /** {@inheritDoc} */ + @Override protected QueryLookupStrategy getQueryLookupStrategy(final QueryLookupStrategy.Key key, + EvaluationContextProvider evaluationCtxProvider) { + + return new QueryLookupStrategy() { + @Override public RepositoryQuery resolveQuery(final Method mtd, final RepositoryMetadata metadata, + final ProjectionFactory factory, NamedQueries namedQueries) { + + final Query annotation = mtd.getAnnotation(Query.class); + + if (annotation != null) { + String qryStr = annotation.value(); + + if (key != Key.CREATE && StringUtils.hasText(qryStr)) + return new IgniteRepositoryQuery(metadata, + new IgniteQuery(qryStr, isFieldQuery(qryStr), IgniteQueryGenerator.getOptions(mtd)), + mtd, factory, ignite.getOrCreateCache(repoToCache.get(metadata.getRepositoryInterface()))); + } + + if (key == QueryLookupStrategy.Key.USE_DECLARED_QUERY) + throw new IllegalStateException("To use QueryLookupStrategy.Key.USE_DECLARED_QUERY, pass " + + "a query string via org.apache.ignite.springdata.repository.Query annotation."); + + return new IgniteRepositoryQuery(metadata, IgniteQueryGenerator.generateSql(mtd, metadata), mtd, + factory, ignite.getOrCreateCache(repoToCache.get(metadata.getRepositoryInterface()))); + } + }; + } + + /** + * @param s + * @return + */ + private boolean isFieldQuery(String s) { + return s.matches("^SELECT.*") && !s.matches("^SELECT\\s+(?:\\w+\\.)?+\\*.*"); + } +} + + + + + + http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/support/IgniteRepositoryFactoryBean.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/support/IgniteRepositoryFactoryBean.java b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/support/IgniteRepositoryFactoryBean.java new file mode 100644 index 0000000..9d1c8c1 --- /dev/null +++ b/modules/spring-data/src/main/java/org/apache/ignite/springdata/repository/support/IgniteRepositoryFactoryBean.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.repository.support; + +import java.io.Serializable; +import org.apache.ignite.springdata.repository.IgniteRepository; +import org.springframework.data.keyvalue.core.KeyValueOperations; +import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory; +import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.parser.AbstractQueryCreator; + +/** + * Apache Ignite repository factory bean. + * + * @param <T> Repository type, {@link IgniteRepository} + * @param <S> Domain object class. + * @param <ID> Domain object key, super expects {@link Serializable}. + */ +public class IgniteRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> + extends KeyValueRepositoryFactoryBean<T, S, ID> { + + /** + * Creates a new {@code IgniteRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + public IgniteRepositoryFactoryBean(Class<? extends T> repositoryInterface) { + super(repositoryInterface); + } + + /** {@inheritDoc} */ + @Override protected KeyValueRepositoryFactory createRepositoryFactory(KeyValueOperations operations, + Class<? extends AbstractQueryCreator<?, ?>> queryCreator, + Class<? extends RepositoryQuery> repositoryQueryType) { + return new IgniteRepositoryFactory(operations, queryCreator, repositoryQueryType); + } +} + http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/test/java/org/apache/ignite/springdata/IgniteSpringDataSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/test/java/org/apache/ignite/springdata/IgniteSpringDataSelfTest.java b/modules/spring-data/src/test/java/org/apache/ignite/springdata/IgniteSpringDataSelfTest.java new file mode 100644 index 0000000..a097c29 --- /dev/null +++ b/modules/spring-data/src/test/java/org/apache/ignite/springdata/IgniteSpringDataSelfTest.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import javax.cache.Cache; +import org.apache.ignite.springdata.misc.FirstRepository; +import org.apache.ignite.springdata.misc.Person; +import org.apache.ignite.springdata.misc.SecondRepository; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; + +/** + * + */ +public class IgniteSpringDataSelfTest extends GridCommonAbstractTest { + /** Repository. */ + private static FirstRepository repo; + + /** Repository 2. */ + private static SecondRepository repo2; + + /** Context. */ + private static AnnotationConfigApplicationContext ctx; + + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + ctx = new AnnotationConfigApplicationContext(); + + ctx.scan("org.apache.ignite.springdata"); + ctx.refresh(); + + repo = ctx.getBean(FirstRepository.class); + repo2 = ctx.getBean(SecondRepository.class); + + for (int i = 0; i < 1000; i++) + repo.save(i, new Person("person" + Integer.toHexString(i), + "lastName" + Integer.toHexString((i + 16) % 256))); + } + + @Override protected void afterTestsStopped() throws Exception { + ctx.destroy(); + + super.afterTestsStopped(); + } + + /** */ + public void testExplicitQuery() { + List<Person> persons = repo.simpleQuery("person4a"); + + assertFalse(persons.isEmpty()); + + for (Person person : persons) + assertEquals("person4a", person.getFirstName()); + } + + /** */ + public void testEqualsPart() { + List<Person> persons = repo.findByFirstName("person4e"); + + assertFalse(persons.isEmpty()); + + for (Person person : persons) + assertEquals("person4e", person.getFirstName()); + } + + /** */ + public void testContainingPart() { + List<Person> persons = repo.findByFirstNameContaining("person4"); + + assertFalse(persons.isEmpty()); + + for (Person person : persons) + assertTrue(person.getFirstName().startsWith("person4")); + } + + /** */ + public void testTopPart() { + Iterable<Person> top = repo.findTopByFirstNameContaining("person4"); + + Iterator<Person> iter = top.iterator(); + + Person person = iter.next(); + + assertFalse(iter.hasNext()); + + assertTrue(person.getFirstName().startsWith("person4")); + } + + /** */ + public void testLikeAndLimit() { + Iterable<Person> like = repo.findFirst10ByFirstNameLike("person"); + + int cnt = 0; + + for (Person next : like) { + assertTrue(next.getFirstName().contains("person")); + + cnt++; + } + + assertEquals(10, cnt); + } + + /** */ + public void testCount() { + int cnt = repo.countByFirstNameLike("person"); + + assertEquals(1000, cnt); + } + + /** */ + public void testCount2() { + int cnt = repo.countByFirstNameLike("person4"); + + assertTrue(cnt < 1000); + } + + /** */ + public void testPageable() { + PageRequest pageable = new PageRequest(1, 5, Sort.Direction.DESC, "firstName"); + + HashSet<String> firstNames = new HashSet<>(); + + List<Person> pageable1 = repo.findByFirstNameRegex("^[a-z]+$", pageable); + + assertEquals(5, pageable1.size()); + + for (Person person : pageable1) { + firstNames.add(person.getFirstName()); + assertTrue(person.getFirstName().matches("^[a-z]+$")); + } + + List<Person> pageable2 = repo.findByFirstNameRegex("^[a-z]+$", pageable.next()); + + assertEquals(5, pageable2.size()); + + for (Person person : pageable2) { + firstNames.add(person.getFirstName()); + assertTrue(person.getFirstName().matches("^[a-z]+$")); + } + + assertEquals(10, firstNames.size()); + } + + /** */ + public void testAndAndOr() { + int cntAnd = repo.countByFirstNameLikeAndSecondNameLike("person1", "lastName1"); + + int cntOr = repo.countByFirstNameStartingWithOrSecondNameStartingWith("person1", "lastName1"); + + assertTrue(cntAnd <= cntOr); + } + + /** */ + public void testQueryWithSort() { + List<Person> persons = repo.queryWithSort("^[a-z]+$", new Sort(Sort.Direction.DESC, "secondName")); + + Person previous = persons.get(0); + + for (Person person : persons) { + assertTrue(person.getSecondName().compareTo(previous.getSecondName()) <= 0); + + assertTrue(person.getFirstName().matches("^[a-z]+$")); + + previous = person; + } + } + + /** */ + public void testQueryWithPaging() { + List<Person> persons = repo.queryWithPageable("^[a-z]+$", new PageRequest(1, 7, Sort.Direction.DESC, "secondName")); + + assertEquals(7, persons.size()); + + Person previous = persons.get(0); + + for (Person person : persons) { + assertTrue(person.getSecondName().compareTo(previous.getSecondName()) <= 0); + + assertTrue(person.getFirstName().matches("^[a-z]+$")); + + previous = person; + } + } + + /** */ + public void testQueryFields() { + List<String> persons = repo.selectField("^[a-z]+$", new PageRequest(1, 7, Sort.Direction.DESC, "secondName")); + + assertEquals(7, persons.size()); + } + + /** */ + public void testFindCacheEntries() { + List<Cache.Entry<Integer, Person>> cacheEntries = repo.findBySecondNameLike("stName1"); + + assertFalse(cacheEntries.isEmpty()); + + for (Cache.Entry<Integer, Person> entry : cacheEntries) + assertTrue(entry.getValue().getSecondName().contains("stName1")); + } + + /** */ + public void testFindOneCacheEntry() { + Cache.Entry<Integer, Person> cacheEntry = repo.findTopBySecondNameLike("tName18"); + + assertNotNull(cacheEntry); + + assertTrue(cacheEntry.getValue().getSecondName().contains("tName18")); + } + + /** */ + public void testFindOneValue() { + Person person = repo.findTopBySecondNameStartingWith("lastName18"); + + assertNotNull(person); + + assertTrue(person.getSecondName().startsWith("lastName18")); + } + + /** */ + public void testSelectSeveralFields() { + List<List> lists = repo.selectSeveralField("^[a-z]+$", new PageRequest(2, 6)); + + assertEquals(6, lists.size()); + + for (List list : lists) { + assertEquals(2, list.size()); + + assertTrue(list.get(0) instanceof Integer); + } + } + + /** */ + public void testCountQuery() { + int cnt = repo.countQuery(".*"); + + assertEquals(256, cnt); + } + + /** */ + public void testSliceOfCacheEntries() { + Slice<Cache.Entry<Integer, Person>> slice = repo2.findBySecondNameIsNot("lastName18", new PageRequest(3, 4)); + + assertEquals(4, slice.getSize()); + + for (Cache.Entry<Integer, Person> entry : slice) + assertFalse("lastName18".equals(entry.getValue().getSecondName())); + } + + /** */ + public void testSliceOfLists() { + Slice<List> lists = repo2.querySliceOfList("^[a-z]+$", new PageRequest(0, 3)); + + assertEquals(3, lists.getSize()); + + for (List list : lists) { + assertEquals(2, list.size()); + + assertTrue(list.get(0) instanceof Integer); + } + } +} + http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/test/java/org/apache/ignite/springdata/IgniteSpringDataTestSuite.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/test/java/org/apache/ignite/springdata/IgniteSpringDataTestSuite.java b/modules/spring-data/src/test/java/org/apache/ignite/springdata/IgniteSpringDataTestSuite.java new file mode 100644 index 0000000..fb8c2a4 --- /dev/null +++ b/modules/spring-data/src/test/java/org/apache/ignite/springdata/IgniteSpringDataTestSuite.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata; + +import junit.framework.TestSuite; + +/** + * + */ +public class IgniteSpringDataTestSuite extends TestSuite { + /** + * @return Test suite. + * @throws Exception Thrown in case of the failure. + */ + public static TestSuite suite() throws Exception { + TestSuite suite = new TestSuite("Spring Data Test Suite"); + + suite.addTest(new IgniteSpringDataSelfTest()); + + return suite; + } +} + http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/ApplicationConfiguration.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/ApplicationConfiguration.java b/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/ApplicationConfiguration.java new file mode 100644 index 0000000..aa7a7ae --- /dev/null +++ b/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/ApplicationConfiguration.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.misc; + +import org.apache.ignite.Ignite; +import org.apache.ignite.Ignition; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.springdata.IgniteKeyValueAdapter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.apache.ignite.springdata.repository.config.EnableIgniteRepositories; +import org.springframework.data.keyvalue.core.KeyValueOperations; +import org.springframework.data.keyvalue.core.KeyValueTemplate; + +/** + * + */ +@Configuration +@EnableIgniteRepositories +public class ApplicationConfiguration { + @Bean + public Ignite igniteInstance() { + IgniteConfiguration cfg = new IgniteConfiguration(); + + CacheConfiguration ccfg = new CacheConfiguration("cache"); + + ccfg.setIndexedTypes(Integer.class, Person.class); + + cfg.setCacheConfiguration(ccfg); + + return Ignition.start(cfg); + } + + @Bean + public KeyValueOperations igniteKeyValueTemplate(Ignite igniteInstance) { + return new KeyValueTemplate(new IgniteKeyValueAdapter(igniteInstance)); + } + + @Bean + public IgniteKeyValueAdapter igniteKeyValueAdapter(Ignite igniteInstance) { + return new IgniteKeyValueAdapter(igniteInstance); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/FirstRepository.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/FirstRepository.java b/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/FirstRepository.java new file mode 100644 index 0000000..315162d --- /dev/null +++ b/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/FirstRepository.java @@ -0,0 +1,92 @@ + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.misc; + +import java.util.Collection; +import java.util.List; +import javax.cache.Cache; +import org.apache.ignite.springdata.repository.Query; +import org.apache.ignite.springdata.repository.config.RepositoryConfig; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.apache.ignite.springdata.repository.IgniteRepository; + +/** + * + */ +@RepositoryConfig(cacheName = "cache") +public interface FirstRepository extends IgniteRepository<Person, Integer> { + /** */ + public List<Person> findByFirstName(String val); + + /** */ + public List<Person> findByFirstNameContaining(String val); + + /** */ + public List<Person> findByFirstNameRegex(String val, Pageable pageable); + + /** */ + public Collection<Person> findTopByFirstNameContaining(String val); + + /** */ + public Iterable<Person> findFirst10ByFirstNameLike(String val); + + /** */ + public int countByFirstNameLike(String val); + + /** */ + public int countByFirstNameLikeAndSecondNameLike(String like1, String like2); + + /** */ + public int countByFirstNameStartingWithOrSecondNameStartingWith(String like1, String like2); + + /** */ + public List<Cache.Entry<Integer, Person>> findBySecondNameLike(String val); + + /** */ + public Cache.Entry<Integer, Person> findTopBySecondNameLike(String val); + + /** */ + public Person findTopBySecondNameStartingWith(String val); + + /** */ + @Query("firstName = ?") + public List<Person> simpleQuery(String val); + + /** */ + @Query("firstName REGEXP ?") + public List<Person> queryWithSort(String val, Sort sort); + + /** */ + @Query("SELECT * FROM Person WHERE firstName REGEXP ?") + public List<Person> queryWithPageable(String val, Pageable pageable); + + /** */ + @Query("SELECT secondName FROM Person WHERE firstName REGEXP ?") + public List<String> selectField(String val, Pageable pageable); + + /** */ + @Query("SELECT _key, secondName FROM Person WHERE firstName REGEXP ?") + public List<List> selectSeveralField(String val, Pageable pageable); + + /** */ + @Query("SELECT count(1) FROM (SELECT DISTINCT secondName FROM Person WHERE firstName REGEXP ?)") + public int countQuery(String val); +} + http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/Person.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/Person.java b/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/Person.java new file mode 100644 index 0000000..502d86d --- /dev/null +++ b/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/Person.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.misc; + +import org.apache.ignite.cache.query.annotations.QuerySqlField; + +public class Person { + /** First name. */ + @QuerySqlField(index = true) + private String firstName; + + /** Second name. */ + @QuerySqlField(index = true) + private String secondName; + + /** + * @param firstName First name. + * @param secondName Second name. + */ + public Person(String firstName, String secondName) { + this.firstName = firstName; + this.secondName = secondName; + } + + /** + * + */ + public String getFirstName() { + return firstName; + } + + /** + * @param firstName First name. + */ + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + /** + * + */ + public String getSecondName() { + return secondName; + } + + /** + * @param secondName Second name. + */ + public void setSecondName(String secondName) { + this.secondName = secondName; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return "Person{" + + "firstName='" + firstName + '\'' + + ", secondName='" + secondName + '\'' + + '}'; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/423709fb/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/SecondRepository.java ---------------------------------------------------------------------- diff --git a/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/SecondRepository.java b/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/SecondRepository.java new file mode 100644 index 0000000..63b0b8f --- /dev/null +++ b/modules/spring-data/src/test/java/org/apache/ignite/springdata/misc/SecondRepository.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.springdata.misc; + +import java.util.List; +import javax.cache.Cache; +import org.apache.ignite.springdata.repository.IgniteRepository; +import org.apache.ignite.springdata.repository.Query; +import org.apache.ignite.springdata.repository.config.RepositoryConfig; +import org.springframework.data.domain.AbstractPageRequest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; + +/** + * + */ +@RepositoryConfig(cacheName = "cache") +public interface SecondRepository extends IgniteRepository<Person, Integer> { + /** */ + public Slice<Cache.Entry<Integer, Person>> findBySecondNameIsNot(String val, PageRequest pageReq); + + /** */ + @Query("SELECT _key, secondName FROM Person WHERE firstName REGEXP ?") + public Slice<List> querySliceOfList(String val, AbstractPageRequest pageReq); +}
