diff --git a/pom.xml b/pom.xml index 2f4a29a6..804775e4 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,11 @@ spring-tx + + org.springframework + spring-web + + org.springframework.data diff --git a/src/main/java/org/springframework/data/gemfire/GemfireUtils.java b/src/main/java/org/springframework/data/gemfire/GemfireUtils.java index 6874441e..307c9ad3 100644 --- a/src/main/java/org/springframework/data/gemfire/GemfireUtils.java +++ b/src/main/java/org/springframework/data/gemfire/GemfireUtils.java @@ -19,22 +19,22 @@ package org.springframework.data.gemfire; import org.apache.geode.cache.CacheFactory; import org.apache.geode.internal.GemFireVersion; import org.springframework.data.gemfire.config.support.GemfireFeature; -import org.springframework.data.gemfire.util.CacheUtils; +import org.springframework.data.gemfire.util.RegionUtils; import org.springframework.util.ClassUtils; import org.w3c.dom.Element; /** - * GemfireUtils is an abstract utility class encapsulating common functionality to access features and capabilities - * of GemFire based on version and other configuration meta-data. + * {@link GemfireUtils} is an abstract utility class encapsulating common functionality to access features + * and capabilities of GemFire based on version and other configuration meta-data. * * @author John Blum - * @see org.springframework.data.gemfire.util.DistributedSystemUtils * @see org.apache.geode.cache.CacheFactory * @see org.apache.geode.cache.Region + * @see org.springframework.data.gemfire.util.RegionUtils * @since 1.3.3 */ @SuppressWarnings("unused") -public abstract class GemfireUtils extends CacheUtils { +public abstract class GemfireUtils extends RegionUtils { public final static String APACHE_GEODE_NAME = "Aache Geode"; public final static String GEMFIRE_NAME = apacheGeodeProductName(); diff --git a/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java b/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java index e3245bce..0c04f244 100644 --- a/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java +++ b/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java @@ -78,6 +78,7 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor } /* (non-Javadoc) */ + // TODO remove this logic and delegate to o.s.d.g.config.remote.GemfireAdminOperations Iterable regionNames() { try { return execute(new ListRegionsOnServerFunction()); diff --git a/src/main/java/org/springframework/data/gemfire/config/admin/AbstractGemfireAdminOperations.java b/src/main/java/org/springframework/data/gemfire/config/admin/AbstractGemfireAdminOperations.java new file mode 100644 index 00000000..746f4853 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/admin/AbstractGemfireAdminOperations.java @@ -0,0 +1,119 @@ +/* + * Copyright 2017 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.config.admin; + +import org.apache.geode.cache.DiskStore; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.lucene.LuceneIndex; +import org.apache.geode.cache.query.Index; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefinition; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; + +/** + * {@link AbstractGemfireAdminOperations} is an abstract base class encapsulating common functionality + * supporting administrative (management) operations against a Pivotal GemFire or Apache Geode cluster. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @since 2.0.0 + */ +public class AbstractGemfireAdminOperations implements GemfireAdminOperations { + + protected static final String NOT_IMPLEMENTED = "Not Implemented"; + + /** + * Returns a {@link Iterable collection} of {@link Region} names defined on the GemFire Servers in the cluster. + * + * @return an {@link Iterable} of {@link Region} names defined on the GemFire Servers in the cluster. + * @see org.apache.geode.cache.Region#getName() + * @see java.lang.Iterable + */ + @Override + public Iterable getAvailableServerRegions() { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * Returns an {@link Iterable} of all the server {@link Region} {@link Index Indexes}. + * + * @return an {@link Iterable} of all the server {@link Region} {@link Index Indexes}. + * @see org.apache.geode.cache.query.Index#getName() + * @see java.lang.Iterable + */ + @Override + public Iterable getAvailableServerRegionIndexes() { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * Creates a cache {@link Region} based on the given {@link RegionDefinition schema object definition}. + * + * @param regionDefinition {@link RegionDefinition} encapsulating configuration meta-data defining + * a cache {@link Region}. + * @see org.springframework.data.gemfire.config.schema.definitions.RegionDefinition + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + */ + @Override + public void createRegion(RegionDefinition regionDefinition) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * Creates a {@link Region} {@link LuceneIndex} based on the given + * {@link SchemaObjectDefinition schema object definition}. + * + * @param luceneIndexDefinition {@link SchemaObjectDefinition} encapsulating the configuration meta-data + * defining a {@link Region} {@link LuceneIndex}. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @see org.apache.geode.cache.lucene.LuceneIndex + * @see org.apache.geode.cache.Region + */ + @Override + public void createLuceneIndex(SchemaObjectDefinition luceneIndexDefinition) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * Creates a {@link Region} OQL {@link Index} based on the given {@link IndexDefinition schema object definition}. + * + * @param indexDefinition {@link IndexDefinition} encapsulating the configuration meta-data + * defining a {@link Region} OQL {@link Index}. + * @see org.springframework.data.gemfire.config.schema.definitions.IndexDefinition + * @see org.apache.geode.cache.query.Index + * @see org.apache.geode.cache.Region + */ + @Override + public void createIndex(IndexDefinition indexDefinition) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + /** + * Creates a {@link DiskStore} based on the given {@link SchemaObjectDefinition schema object definition}. + * + * @param diskStoreDefinition {@link SchemaObjectDefinition} encapsulating the configuration meta-data + * defining a {@link DiskStore}. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @see org.apache.geode.cache.DiskStore + */ + @Override + public void createDiskStore(SchemaObjectDefinition diskStoreDefinition) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/admin/GemfireAdminOperations.java b/src/main/java/org/springframework/data/gemfire/config/admin/GemfireAdminOperations.java new file mode 100644 index 00000000..93c26a1f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/admin/GemfireAdminOperations.java @@ -0,0 +1,141 @@ +/* + * Copyright 2017 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.config.admin; + +import static java.util.Arrays.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; + +import org.apache.geode.cache.DiskStore; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.lucene.LuceneIndex; +import org.apache.geode.cache.query.Index; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefinition; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; + +/** + * The {@link GemfireAdminOperations} interface defines a set of operations to define schema objects in a remote + * Apache Geode or Pivotal GemFire cluster. + * + * @author John Blum + * @see org.apache.geode.cache.DiskStore + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.lucene.LuceneIndex + * @see org.apache.geode.cache.query.Index + * @see org.springframework.data.gemfire.config.schema.definitions.IndexDefinition + * @see org.springframework.data.gemfire.config.schema.definitions.RegionDefinition + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @since 2.0.0 + */ +@SuppressWarnings("unused") +public interface GemfireAdminOperations { + + /** + * Returns a {@link Iterable collection} of {@link Region} names defined on the GemFire Servers in the cluster. + * + * @return an {@link Iterable} of {@link Region} names defined on the GemFire Servers in the cluster. + * @see org.apache.geode.cache.Region#getName() + * @see java.lang.Iterable + */ + Iterable getAvailableServerRegions(); + + /** + * Returns an {@link Iterable} of all the server {@link Region} {@link Index Indexes}. + * + * @return an {@link Iterable} of all the server {@link Region} {@link Index Indexes}. + * @see org.apache.geode.cache.query.Index#getName() + * @see java.lang.Iterable + */ + Iterable getAvailableServerRegionIndexes(); + + /** + * Creates a cache {@link Region} based on the given {@link RegionDefinition schema object definition}. + * + * @param regionDefinition {@link RegionDefinition} encapsulating configuration meta-data defining + * a cache {@link Region}. + * @see org.springframework.data.gemfire.config.schema.definitions.RegionDefinition + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + */ + void createRegion(RegionDefinition regionDefinition); + + default void createRegions(RegionDefinition... regionDefinitions) { + stream(nullSafeArray(regionDefinitions, RegionDefinition.class)).forEach(this::createRegion); + } + + default void createRegions(Iterable regionDefinitions) { + nullSafeIterable(regionDefinitions).forEach(this::createRegion); + } + + /** + * Creates a {@link Region} {@link LuceneIndex} based on the given + * {@link SchemaObjectDefinition schema object definition}. + * + * @param luceneIndexDefinition {@link SchemaObjectDefinition} encapsulating the configuration meta-data + * defining a {@link Region} {@link LuceneIndex}. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @see org.apache.geode.cache.lucene.LuceneIndex + * @see org.apache.geode.cache.Region + */ + void createLuceneIndex(SchemaObjectDefinition luceneIndexDefinition); + + default void createLuceneIndexes(SchemaObjectDefinition... luceneIndexDefinitions) { + stream(nullSafeArray(luceneIndexDefinitions, SchemaObjectDefinition.class)).forEach(this::createLuceneIndex); + } + + default void createLuceneIndexes(Iterable luceneIndexDefinitions) { + nullSafeIterable(luceneIndexDefinitions).forEach(this::createLuceneIndex); + } + + /** + * Creates a {@link Region} OQL {@link Index} based on the given {@link IndexDefinition schema object definition}. + * + * @param indexDefinition {@link IndexDefinition} encapsulating the configuration meta-data + * defining a {@link Region} OQL {@link Index}. + * @see org.springframework.data.gemfire.config.schema.definitions.IndexDefinition + * @see org.apache.geode.cache.query.Index + * @see org.apache.geode.cache.Region + */ + void createIndex(IndexDefinition indexDefinition); + + default void createIndexes(IndexDefinition... indexDefinitions) { + stream(nullSafeArray(indexDefinitions, IndexDefinition.class)).forEach(this::createIndex); + } + + default void createIndexes(Iterable indexDefinitions) { + nullSafeIterable(indexDefinitions).forEach(this::createIndex); + } + + /** + * Creates a {@link DiskStore} based on the given {@link SchemaObjectDefinition schema object definition}. + * + * @param diskStoreDefinition {@link SchemaObjectDefinition} encapsulating the configuration meta-data + * defining a {@link DiskStore}. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @see org.apache.geode.cache.DiskStore + */ + void createDiskStore(SchemaObjectDefinition diskStoreDefinition); + + default void createDiskStores(SchemaObjectDefinition... diskStoreDefinitions) { + stream(nullSafeArray(diskStoreDefinitions, SchemaObjectDefinition.class)).forEach(this::createDiskStore); + } + + default void createDiskStores(Iterable diskStoreDefinitions) { + nullSafeIterable(diskStoreDefinitions).forEach(this::createDiskStore); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/admin/functions/CreateIndexFunction.java b/src/main/java/org/springframework/data/gemfire/config/admin/functions/CreateIndexFunction.java new file mode 100644 index 00000000..fb631ae0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/admin/functions/CreateIndexFunction.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017 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.config.admin.functions; + +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.query.QueryException; +import org.apache.geode.cache.query.QueryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.gemfire.GemfireCacheUtils; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; +import org.springframework.data.gemfire.function.annotation.GemfireFunction; + +/** + * The CreateIndexFunction class... + * + * @author John Blum + * @since 1.0.0 + */ +public class CreateIndexFunction { + + public static final String CREATE_INDEX_FUNCTION_ID = "CreateOqlIndexFunction"; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @GemfireFunction(id = CREATE_INDEX_FUNCTION_ID) + public boolean createIndex(IndexDefinition indexDefinition) { + + Cache gemfireCache = resolveCache(); + + if (isNonExistingIndex(gemfireCache, indexDefinition)) { + + if (logger.isInfoEnabled()) { + logger.info("Creating Index with name [{}] having expression [{}] on Region [{}] with type [{}]", + indexDefinition.getName(), indexDefinition.getExpression(), indexDefinition.getFromClause(), + indexDefinition.getIndexType()); + } + + QueryService queryService = gemfireCache.getQueryService(); + + try { + switch (indexDefinition.getIndexType()) { + case KEY: + case PRIMARY_KEY: + queryService.createKeyIndex(indexDefinition.getName(), + indexDefinition.getExpression(), indexDefinition.getFromClause()); + return true; + case HASH: + queryService.createHashIndex(indexDefinition.getName(), + indexDefinition.getExpression(), indexDefinition.getFromClause()); + return true; + case FUNCTIONAL: + queryService.createIndex(indexDefinition.getName(), + indexDefinition.getExpression(), indexDefinition.getFromClause()); + return true; + default: + return false; + } + } + catch (QueryException cause) { + throw GemfireCacheUtils.convertGemfireAccessException(cause); + } + } + else { + + if (logger.isInfoEnabled()) { + logger.info("Index with name [{}] already exists", indexDefinition.getName()); + } + + return false; + } + } + + protected Cache resolveCache() { + return CacheFactory.getAnyInstance(); + } + + protected boolean isNonExistingIndex(GemFireCache gemfireCache, IndexDefinition indexDefinition) { + return !nullSafeCollection(gemfireCache.getQueryService().getIndexes()).stream() + .anyMatch(index -> index.getName().equals(indexDefinition.getName())); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/admin/functions/CreateRegionFunction.java b/src/main/java/org/springframework/data/gemfire/config/admin/functions/CreateRegionFunction.java new file mode 100644 index 00000000..43fbb736 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/admin/functions/CreateRegionFunction.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 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.config.admin.functions; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; +import org.springframework.data.gemfire.function.annotation.GemfireFunction; + +/** + * The CreateRegionFunction class... + * + * @author John Blum + * @since 1.0.0 + */ +public class CreateRegionFunction { + + public static final String CREATE_REGION_FUNCTION_ID = "CreateRegionFunction"; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @GemfireFunction(id = CREATE_REGION_FUNCTION_ID) + public boolean createRegion(RegionDefinition regionDefinition) { + + Cache gemfireCache = resolveCache(); + + if (isNonExistingRegion(gemfireCache, regionDefinition)) { + + RegionFactory regionFactory = gemfireCache.createRegionFactory(regionDefinition.getRegionShortcut()); + + Region region = regionFactory.create(regionDefinition.getName()); + + if (logger.isInfoEnabled()) { + logger.info("Created Region [{}] of type [{}]", region.getName(), region.getAttributes().getDataPolicy()); + } + + return true; + } + else { + + if (logger.isDebugEnabled()) { + logger.info("Region with name [{}] already exists", regionDefinition.getName()); + } + + return false; + } + } + + protected Cache resolveCache() { + return CacheFactory.getAnyInstance(); + } + + private boolean isNonExistingRegion(Cache gemfireCache, RegionDefinition regionDefinition) { + return (gemfireCache.getRegion(regionDefinition.getName()) == null); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/admin/functions/ListIndexesFunction.java b/src/main/java/org/springframework/data/gemfire/config/admin/functions/ListIndexesFunction.java new file mode 100644 index 00000000..50b1559c --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/admin/functions/ListIndexesFunction.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 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.config.admin.functions; + +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.cache.query.Index; +import org.springframework.data.gemfire.function.annotation.GemfireFunction; + +/** + * The ListIndexesFunction class... + * + * @author John Blum + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public class ListIndexesFunction { + + public static final String LIST_INDEXES_FUNCTION_ID = "ListQqlIndexesFunction"; + + @GemfireFunction(id = LIST_INDEXES_FUNCTION_ID) + public Set listIndexes() { + + return Optional.ofNullable(resolveCache()) + .map(cache -> cache.getQueryService()) + .map(queryService -> + nullSafeCollection(queryService.getIndexes()).stream().map(Index::getName).collect(Collectors.toSet())) + .orElseGet(Collections::emptySet); + } + + protected Cache resolveCache() { + return CacheFactory.getAnyInstance(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/admin/remote/FunctionGemfireAdminTemplate.java b/src/main/java/org/springframework/data/gemfire/config/admin/remote/FunctionGemfireAdminTemplate.java new file mode 100644 index 00000000..8bd525fd --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/admin/remote/FunctionGemfireAdminTemplate.java @@ -0,0 +1,171 @@ +/* + * Copyright 2017 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.config.admin.remote; + +import static java.util.Arrays.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; + +import java.util.Collections; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.execute.Function; +import org.apache.geode.cache.query.Index; +import org.apache.geode.management.internal.cli.domain.RegionInformation; +import org.apache.geode.management.internal.cli.functions.GetRegionsFunction; +import org.springframework.data.gemfire.client.function.ListRegionsOnServerFunction; +import org.springframework.data.gemfire.config.admin.AbstractGemfireAdminOperations; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.config.admin.functions.CreateIndexFunction; +import org.springframework.data.gemfire.config.admin.functions.CreateRegionFunction; +import org.springframework.data.gemfire.config.admin.functions.ListIndexesFunction; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; +import org.springframework.data.gemfire.function.execution.GemfireFunctionOperations; +import org.springframework.data.gemfire.function.execution.GemfireOnServersFunctionTemplate; +import org.springframework.util.Assert; + +/** + * The {@link FunctionGemfireAdminTemplate} class is an implementation of the {@link GemfireAdminOperations} interface + * supporting the Pivotal GemFire / Apache Geode administrative functions/operations via {@link Function} execution + * in the cluster. + * + * Note: any schema changing functionality (e.g. {@link #createRegion(RegionDefinition)}) does not get recorded by + * the GemFire/Geode Cluster Configuration Service using this strategy. + * + * @author John Blum + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.execute.Function + * @see org.springframework.data.gemfire.client.function.ListRegionsOnServerFunction + * @see org.springframework.data.gemfire.config.admin.AbstractGemfireAdminOperations + * @see org.springframework.data.gemfire.function.execution.GemfireOnServersFunctionTemplate + * @since 2.0.0 + */ +public class FunctionGemfireAdminTemplate extends AbstractGemfireAdminOperations { + + private final ClientCache clientCache; + + /** + * Constructs a new instance of the {@link FunctionGemfireAdminTemplate} initialized with + * a {@link ClientCache} instance. + * + * @param clientCache reference to a {@link ClientCache} instance. + * @throws IllegalArgumentException if {@link ClientCache} is {@literal null}. + * @see org.apache.geode.cache.client.ClientCache + */ + public FunctionGemfireAdminTemplate(ClientCache clientCache) { + + Assert.notNull(clientCache, "ClientCache is required"); + + this.clientCache = clientCache; + } + + /** + * Returns a reference to the configured {@link ClientCache} instance. + * + * @return a reference to the configured {@link ClientCache} instance. + * @see org.apache.geode.cache.client.ClientCache + */ + protected ClientCache getClientCache() { + return this.clientCache; + } + + /** + * Lists all available {@link Region Regions} configured for all servers in the remote Pivotal GemFire + * / Apache Geode cluster. + * + * @return an {@link Iterable} of servers-side {@link Region} names for all {@link Region Regions} defined + * across all servers in the remote GemFire/Geode cluster. + * @see java.lang.Iterable + */ + @Override + public Iterable getAvailableServerRegions() { + + try { + return execute(new ListRegionsOnServerFunction()); + } + catch (Exception cause) { + try { + return Optional.ofNullable(execute(new GetRegionsFunction())) + .filter(this::containsRegionInformation) + .map(regionInformationArray -> + stream(nullSafeArray((Object[]) regionInformationArray, Object.class)) + .map(regionInformation -> ((RegionInformation) regionInformation).getName()) + .collect(Collectors.toSet()) + ) + .orElse(Collections.emptySet()); + } + catch (Exception ignore) { + return Collections.emptySet(); + } + } + } + + /** + * Returns an {@link Iterable} of all the server {@link Region} {@link Index Indexes}. + * + * @return an {@link Iterable} of all the server {@link Region} {@link Index Indexes}. + * @see org.apache.geode.cache.query.Index#getName() + * @see java.lang.Iterable + */ + @Override + public Iterable getAvailableServerRegionIndexes() { + return execute(ListIndexesFunction.LIST_INDEXES_FUNCTION_ID); + } + + @Override + public void createRegion(RegionDefinition regionDefinition) { + execute(CreateRegionFunction.CREATE_REGION_FUNCTION_ID, regionDefinition); + } + + @Override + public void createIndex(IndexDefinition indexDefinition) { + execute(CreateIndexFunction.CREATE_INDEX_FUNCTION_ID, indexDefinition); + } + + /* (non-Javadoc) */ + T execute(Function gemfireFunction, Object... arguments) { + return newGemfireFunctionOperations().executeAndExtract(gemfireFunction, arguments); + } + + /* (non-Javadoc) */ + T execute(String gemfireFunctionId, Object... arguments) { + return newGemfireFunctionOperations().executeAndExtract(gemfireFunctionId, arguments); + } + + /* (non-Javadoc) */ + protected GemfireFunctionOperations newGemfireFunctionOperations() { + return newGemfireFunctionOperations(getClientCache()); + } + + /* (non-Javadoc) */ + protected GemfireFunctionOperations newGemfireFunctionOperations(ClientCache clientCache) { + return new GemfireOnServersFunctionTemplate(clientCache); + } + + /* (non-Javadoc) */ + boolean containsRegionInformation(Object results) { + + return Optional.ofNullable(results) + .filter(it -> it instanceof Object[]) + .filter(it -> ((Object[]) it).length > 0) + .filter(it -> ((Object[]) it)[0] instanceof RegionInformation) + .isPresent(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplate.java b/src/main/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplate.java new file mode 100644 index 00000000..0bad2916 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplate.java @@ -0,0 +1,198 @@ +/* + * Copyright 2017 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.config.admin.remote; + +import java.net.URI; + +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.execute.Function; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +/** + * {@link RestHttpGemfireAdminTemplate} is class implementing the {@link GemfireAdminOperations} interface, + * extending the {@link FunctionGemfireAdminTemplate} to support administrative (management) operations + * on a Pivotal GemFire or Apache Geode cluster using the Management REST API interface over HTTP. + * + * The fallback is using {@link Function} execution if the a particular administrative (management) operation + * is not supported or has not been implemented against the Management REST API interface over HTTP. + * + * @author John Blum + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.execute.Function + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + * @see org.springframework.data.gemfire.config.admin.remote.FunctionGemfireAdminTemplate + * @since 2.0.0 + */ +public class RestHttpGemfireAdminTemplate extends FunctionGemfireAdminTemplate { + + protected static final boolean CREATE_REGION_SKIP_IF_EXISTS_DEFAULT = true; + + protected static final int DEFAULT_PORT = 7070; + + protected static final String DEFAULT_HOST = "localhost"; + + protected static final String MANAGEMENT_REST_API_URL_TEMPLATE = "http://%1$s:%2$d/gemfire/v1/"; + + private final RestOperations restTemplate; + + private final String managementRestApiUrl; + + /** + * Constructs an instance of the {@link RestHttpGemfireAdminTemplate} initialized with + * the given {@link ClientCache} and configured with the default host and port when accessing + * the GemFire or Geode Management REST API interface. + * + * @param clientCache reference to the {@link ClientCache} + * @throws IllegalArgumentException if the {@link ClientCache} reference is {@literal null}. + * @see org.apache.geode.cache.client.ClientCache + */ + public RestHttpGemfireAdminTemplate(ClientCache clientCache) { + this(clientCache, DEFAULT_HOST, DEFAULT_PORT); + } + + /** + * Constructs an instance of the {@link RestHttpGemfireAdminTemplate} initialized with + * the given {@link ClientCache} and configured with the specified host and port when accessing + * the GemFire or Geode Management REST API interface. + * + * @param clientCache reference to the {@link ClientCache} + * @param host {@link String} containing the hostname of the GemFire/Geode Manager. + * @param port integer value specifying the port on which the GemFire/Geode Manager HTTP Service is listening + * for HTTP clients. + * @throws IllegalArgumentException if the {@link ClientCache} reference is {@literal null}. + * @see org.apache.geode.cache.client.ClientCache + */ + public RestHttpGemfireAdminTemplate(ClientCache clientCache, String host, int port) { + + super(clientCache); + + this.restTemplate = newRestOperations(); + this.managementRestApiUrl = resolveManagementRestApiUrl(host, port); + } + + /** + * Constructs a new instance of the Spring {@link RestTemplate} to perform REST API operations over HTTP. + * + * @return a new instance of Spring's {@link RestTemplate}. + * @see org.springframework.http.client.SimpleClientHttpRequestFactory + * @see org.springframework.web.client.RestOperations + * @see org.springframework.web.client.RestTemplate + */ + RestOperations newRestOperations() { + return new RestTemplate(new SimpleClientHttpRequestFactory()); + } + + /** + * Resolves the Pivotal GemFire or Apache Geode Management REST API URL given the host and port + * of the GemFire/Geode Manager's embedded HTTP service. + * + * @param host {@link String} containing the hostname of the Manager running the embedded HTTP service + * and Management REST API. + * @param port integer specifying the port that the embedded Manager's HTTP service is listening on. + * @return the resolved URL. + */ + private String resolveManagementRestApiUrl(String host, int port) { + return String.format(MANAGEMENT_REST_API_URL_TEMPLATE, host, port); + } + + /** + * Returns a reference to the resolved GemFire/Geode Management REST API URL. + * + * @return a {@link String} containing the resolved GemFire/Geode Management REST API URL. + */ + protected String getManagementRestApiUrl() { + return this.managementRestApiUrl; + } + + /** + * Returns a reference to the {@link RestOperations} used to perform REST API calls. + * + * @return a reference to the {@link RestOperations} used to perform REST API calls. + * @see org.springframework.web.client.RestOperations + */ + protected RestOperations getRestOperations() { + return this.restTemplate; + } + + @Override + public void createIndex(IndexDefinition indexDefinition) { + + HttpHeaders httpHeaders = new HttpHeaders(); + + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + // HTTP Message Body + MultiValueMap requestParameters = new LinkedMultiValueMap<>(); + + requestParameters.add("name", indexDefinition.getName()); + requestParameters.add("expression", indexDefinition.getExpression()); + requestParameters.add("region", indexDefinition.getFromClause()); + requestParameters.add("type", indexDefinition.getIndexType().toString()); + + RequestEntity> requestEntity = + new RequestEntity<>(requestParameters, httpHeaders, HttpMethod.POST, resolveCreateIndexUri()); + + ResponseEntity response = getRestOperations().exchange(requestEntity, String.class); + + // TODO do something with result; e.g. log when failure (or when not "OK") + HttpStatus.OK.equals(response.getStatusCode()); + } + + protected URI resolveCreateIndexUri() { + return URI.create(getManagementRestApiUrl().concat("indexes")); + } + + @Override + public void createRegion(RegionDefinition regionDefinition) { + + HttpHeaders httpHeaders = new HttpHeaders(); + + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + // HTTP Message Body + MultiValueMap requestParameters = new LinkedMultiValueMap<>(); + + requestParameters.add("name", regionDefinition.getName()); + requestParameters.add("type", regionDefinition.getRegionShortcut().toString()); + requestParameters.add("skip-if-exists", String.valueOf(CREATE_REGION_SKIP_IF_EXISTS_DEFAULT)); + + RequestEntity> requestEntity = + new RequestEntity<>(requestParameters, httpHeaders, HttpMethod.POST, resolveCreateRegionUri()); + + ResponseEntity response = getRestOperations().exchange(requestEntity, String.class); + + // TODO do something with result; e.g. log when failure (or when not "OK") + HttpStatus.OK.equals(response.getStatusCode()); + } + + protected URI resolveCreateRegionUri() { + return URI.create(getManagementRestApiUrl().concat("regions")); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationConfiguration.java new file mode 100644 index 00000000..89177eed --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationConfiguration.java @@ -0,0 +1,286 @@ +/* + * Copyright 2017 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.config.annotation; + +import static java.util.stream.StreamSupport.stream; +import static org.springframework.data.gemfire.util.CacheUtils.isClient; + +import java.lang.annotation.Annotation; +import java.util.Optional; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.query.Index; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportAware; +import org.springframework.context.event.ApplicationContextEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.OrderComparator; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.GemfireUtils; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.config.admin.remote.FunctionGemfireAdminTemplate; +import org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate; +import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport; +import org.springframework.data.gemfire.config.schema.SchemaObjectCollector; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefiner; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; +import org.springframework.data.gemfire.config.schema.support.ClientRegionCollector; +import org.springframework.data.gemfire.config.schema.support.ComposableSchemaObjectCollector; +import org.springframework.data.gemfire.config.schema.support.ComposableSchemaObjectDefiner; +import org.springframework.data.gemfire.config.schema.support.IndexCollector; +import org.springframework.data.gemfire.config.schema.support.IndexDefiner; +import org.springframework.data.gemfire.config.schema.support.RegionDefiner; + +/** + * Spring {@link Configuration @Configuration} class defining Spring beans that will record the creation of + * Apache Geode / Pivotal GemFire {@link Region Regions} defined in Spring config (i.e. XML, Java or by Annotations) + * as Spring beans in the Spring container. + * + * @author John Blum + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.query.Index + * @see org.springframework.beans.factory.config.BeanPostProcessor + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.ImportAware + * @see org.springframework.context.event.EventListener + * @since 2.0.0 + */ +@Configuration +@SuppressWarnings("unused") +public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { + + protected static final boolean DEFAULT_MANAGEMENT_USE_HTTP = false; + + protected static final int DEFAULT_MANAGEMENT_HTTP_PORT = HttpServiceConfiguration.DEFAULT_HTTP_SERVICE_PORT; + + protected static final String DEFAULT_MANAGEMENT_HTTP_HOST = "localhost"; + + private static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = RegionDefinition.DEFAULT_REGION_SHORTCUT; + + private Boolean useHttp = DEFAULT_MANAGEMENT_USE_HTTP; + + private Integer managementHttpPort = DEFAULT_MANAGEMENT_HTTP_PORT; + + private RegionShortcut serverRegionShortcut; + + private String managementHttpHost = DEFAULT_MANAGEMENT_HTTP_HOST; + + @Override + protected Class getAnnotationType() { + return EnableClusterConfiguration.class; + } + + protected void setManagementHttpHost(String hostname) { + this.managementHttpHost = hostname; + } + + protected Optional getManagementHttpHost() { + return Optional.ofNullable(this.managementHttpHost); + } + + protected String resolveManagementHttpHost() { + return getManagementHttpHost().orElse(DEFAULT_MANAGEMENT_HTTP_HOST); + } + + protected void setManagementHttpPort(Integer managementHttpPort) { + this.managementHttpPort = managementHttpPort; + } + + protected Optional getManagementHttpPort() { + return Optional.ofNullable(this.managementHttpPort); + } + + protected int resolveManagementHttpPort() { + return getManagementHttpPort().orElse(DEFAULT_MANAGEMENT_HTTP_PORT); + } + + protected void setManagementUseHttp(Boolean useHttp) { + this.useHttp = useHttp; + } + + protected Optional getManagementUseHttp() { + return Optional.ofNullable(this.useHttp); + } + + protected boolean resolveManagementUseHttp() { + return getManagementUseHttp().orElse(DEFAULT_MANAGEMENT_USE_HTTP); + } + + protected void setServerRegionShortcut(RegionShortcut regionShortcut) { + this.serverRegionShortcut = regionShortcut; + } + + protected Optional getServerRegionShortcut() { + return Optional.ofNullable(this.serverRegionShortcut); + } + + protected RegionShortcut resolveServerRegionShortcut() { + return getServerRegionShortcut().orElse(DEFAULT_SERVER_REGION_SHORTCUT); + } + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + + if (isAnnotationPresent(importMetadata)) { + + AnnotationAttributes enableClusterConfigurationAttributes = getAnnotationAttributes(importMetadata); + + setManagementHttpHost(resolveProperty(managementProperty("http.hostname"), + DEFAULT_MANAGEMENT_HTTP_HOST)); + + setManagementHttpPort(resolveProperty(managementProperty("http.port"), + DEFAULT_MANAGEMENT_HTTP_PORT)); + + setManagementUseHttp(resolveProperty(managementProperty("use-http"), + DEFAULT_MANAGEMENT_USE_HTTP)); + + setServerRegionShortcut(resolveProperty(clusterProperty("region.type"), + RegionShortcut.class, DEFAULT_SERVER_REGION_SHORTCUT)); + } + } + + @EventListener + public void gemfireClusterSchemaCreationHandler(ContextRefreshedEvent event) { + + GemFireCache gemfireCache = resolveGemFireCache(event); + + if (isClient(gemfireCache)) { + + GemfireAdminOperations gemfireAdminOperations = newGemfireAdminOperations((ClientCache) gemfireCache); + + SchemaObjectDefiner schemaObjectDefiner = newSchemaObjectDefiner(); + + Iterable schemaObjects = newSchemaObjectCollector().collectFrom(resolveApplicationContext(event)); + + stream(schemaObjects.spliterator(), false) + .map(schemaObjectDefiner::define) + .sorted(OrderComparator.INSTANCE) + .forEach(schemaObjectDefinition -> + schemaObjectDefinition.ifPresent(it -> it.create(gemfireAdminOperations))); + } + /* + else if (isPeer(gemfireCache)) { + + GemfireFunctionUtils.registerFunctionForPojoMethod(new CreateRegionFunction(), + CreateRegionFunction.CREATE_REGION_FUNCTION_ID); + + GemfireFunctionUtils.registerFunctionForPojoMethod(new CreateIndexFunction(), + CreateIndexFunction.CREATE_INDEX_FUNCTION_ID); + } + */ + } + + /** + * Resolves a reference to the Spring {@link ApplicationContext} from the given {@link ApplicationContextEvent}. + * + * @param event {@link ApplicationContextEvent} from which to resolve the Spring {@link ApplicationContext}. + * @return the resolved Spring {@link ApplicationContext}. + * @see org.springframework.context.event.ApplicationContextEvent + * @see org.springframework.context.ApplicationContext + */ + private ApplicationContext resolveApplicationContext(ApplicationContextEvent event) { + return event.getApplicationContext(); + } + + /** + * Tries to resolve the {@link GemFireCache} from the Spring {@link ApplicationContext}. + * The {@link GemFireCache} will be resolvable from the Spring {@link ApplicationContext} if the cache + * was registered a managed bean in the Spring container. + * + * If the {@link GemFireCache} cannot be resolved from the {@link ApplicationContext}, this method will attempt + * to resolve the cache reference from GemFire's global context using the GemFire API. + * + * @param event Spring {@link ApplicationContextEvent} encapsulating the details of the Spring container event. + * @return the resolved {@link GemFireCache} if available. + * @see org.springframework.context.event.ApplicationContextEvent + * @see org.apache.geode.cache.GemFireCache + */ + private GemFireCache resolveGemFireCache(ApplicationContextEvent event) { + + try { + return resolveApplicationContext(event).getBean(GemFireCache.class); + } + catch (BeansException ignore) { + return GemfireUtils.resolveGemFireCache(); + } + } + + /** + * Constructs an instance of {@link GemfireAdminOperations} to perform administrative, schema functions + * on a GemFire cache cluster as well as a client cache from a cache client. + * + * @param clientCache {@link ClientCache} instance used by the {@link GemfireAdminOperations} interface + * to access the GemFire system. + * @return an implementation of the {@link GemfireAdminOperations} interface to perform administrative functions + * on a GemFire system. + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + * @see org.apache.geode.cache.client.ClientCache + */ + private GemfireAdminOperations newGemfireAdminOperations(ClientCache clientCache) { + + if (resolveManagementUseHttp()) { + + String host = resolveManagementHttpHost(); + int port = resolveManagementHttpPort(); + + return new RestHttpGemfireAdminTemplate(clientCache, host, port); + } + else { + return new FunctionGemfireAdminTemplate(clientCache); + } + } + + /** + * Constructs an instance of {@link SchemaObjectCollector} to inspect the application's context + * and find all the GemFire schema objects declared of a particular type or types. + * + * @return a new instance of {@link SchemaObjectCollector} to inspect a GemFire system schema + * in search of specific GemFire schema objects (e.g. {@link Region} or {@link Index}). + * @see org.springframework.data.gemfire.config.schema.SchemaObjectCollector + */ + private SchemaObjectCollector newSchemaObjectCollector() { + + return ComposableSchemaObjectCollector.compose( + new ClientRegionCollector(), + new IndexCollector() + ); + } + + /** + * Constructs an instance of {@link SchemaObjectDefiner} used to reverse engineer a GemFire schema object instance + * to build a definition. + * + * @return a new instance of {@link SchemaObjectDefiner}. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefiner + */ + private SchemaObjectDefiner newSchemaObjectDefiner() { + + return ComposableSchemaObjectDefiner.compose( + new RegionDefiner(resolveServerRegionShortcut()), + new IndexDefiner() + ); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.java new file mode 100644 index 00000000..1f1bed84 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.java @@ -0,0 +1,102 @@ +/* + * Copyright 2017 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.config.annotation; + +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; + +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.springframework.context.annotation.Import; + +/** + * The {@link EnableClusterConfiguration} annotation enables Apache Geode / Pivotal GemFire schema-like definitions + * defined in a Spring [Boot], Geode/GemFire cache client application using Spring config to be pushed to + * a Geode/GemFire cluster, similar to how schema commands (e.g. `create region`) in Gfsh are processed by + * an Geode/GemFire Manager. + * + * @author John Blum + * @see java.lang.annotation.Documented + * @see java.lang.annotation.Inherited + * @see java.lang.annotation.Retention + * @see java.lang.annotation.Target + * @see org.springframework.context.annotation.Import + * @see org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration + * @since 2.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(ClusterConfigurationConfiguration.class) +@SuppressWarnings("unused") +public @interface EnableClusterConfiguration { + + /** + * Configures the bind address used by the Spring, GemFire/Geode cache client application to locate + * the Manager's HTTP Service and access the Management REST API. This configuration setting is only used + * when {@link #useHttp()} is set to {@literal true}. + * + * Alternatively, you can configure this setting using the {@literal spring.data.gemfire.management.http.host} + * property in {@literal application.properties}. + * + * Defaults to {@literal localhost}. + */ + String host() default ClusterConfigurationConfiguration.DEFAULT_MANAGEMENT_HTTP_HOST; + + /** + * Configures the port used by the Spring, GemFire/Geode cache client application to locate + * the Manager's HTTP Service and access the Management REST API. This configuration setting is only used + * when {@link #useHttp()} is set to {@literal true}. + * + * Alternatively, you can configure this setting using the {@literal spring.data.gemfire.management.http.port} + * property in {@literal application.properties}. + * + * Defaults to {@literal 7070}. + */ + int port() default ClusterConfigurationConfiguration.DEFAULT_MANAGEMENT_HTTP_PORT; + + /** + * Configuration setting used to specify the data management policy used when creating {@link Region Regions} + * on the servers in the Geode/GemFire cluster. + * + * The data management policy is expressed with a {@link RegionShortcut}, but corresponds to the various + * different {@link DataPolicy DataPolicies} available. + * + * Alternatively, you can configure this setting using the {@literal spring.data.gemfire.cluster.region.type} + * property in {@literal application.properties}. + * + * Defaults to {@link RegionShortcut#PARTITION}. + */ + RegionShortcut serverRegionShortcut() default RegionShortcut.PARTITION; + + /** + * Configures whether connectivity between the Spring, GemFire/Geode application should be established using HTTP. + * + * Alternatively, you can configure this setting using the {@literal spring.data.gemfire.management.use-http} + * property in {@literal application.properties}. + * + * Defaults to {@literal false}. + */ + boolean useHttp() default ClusterConfigurationConfiguration.DEFAULT_MANAGEMENT_USE_HTTP; + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java index b786926f..bf2e34fd 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java @@ -541,6 +541,11 @@ public abstract class AbstractAnnotationConfigSupport return String.format("%1$s%2$s.%3$s", propertyName("cache.server."), name, propertyNameSuffix); } + /* (non-Javadoc) */ + protected String clusterProperty(String propertyNameSuffix) { + return String.format("%1$s%2$s", propertyName("cluster."), propertyNameSuffix); + } + /* (non-Javadoc) */ protected String diskStoreProperty(String propertyNameSuffix) { return String.format("%1$s%2$s", propertyName("disk.store."), propertyNameSuffix); @@ -561,6 +566,11 @@ public abstract class AbstractAnnotationConfigSupport return String.format("%1$s%2$s", propertyName("logging."), propertyNameSuffix); } + /* (non-Javadoc) */ + protected String managementProperty(String propertyNameSuffix) { + return String.format("%1$s%2$s", propertyName("management."), propertyNameSuffix); + } + /* (non-Javadoc) */ protected String managerProperty(String propertyNameSuffix) { return String.format("%1$s%2$s", propertyName("manager."), propertyNameSuffix); diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectCollector.java b/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectCollector.java new file mode 100644 index 00000000..4971ddf5 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectCollector.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 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.config.schema; + +import java.util.Collections; +import java.util.Set; + +import org.apache.geode.cache.GemFireCache; +import org.springframework.context.ApplicationContext; + +/** + * The {@link SchemaObjectCollector} interface defines a contract for implementing objects to search for + * and find all schema objects of a particular type in a given context. + * + * Implementations of this interface know how to inspect the given context and find all references + * to the schema object instances of a particular type. + * + * @author John Blum + * @see org.apache.geode.cache.GemFireCache + * @see org.springframework.context.ApplicationContext + * @since 2.0.0 + */ +public interface SchemaObjectCollector { + + /** + * Collects all schema objects of type {@link T} declared in the given {@link ApplicationContext}. + * + * @param applicationContext Spring {@link ApplicationContext} from which to collect schema objects + * of type {@link T}. + * @return a {@link Set} of all schema objects of type {@link T} declared in the {@link ApplicationContext}; + * returns an empty {@link Set} if no schema object of type {@link T} could be found. + * @see org.springframework.context.ApplicationContext + * @see java.lang.Iterable + */ + default Iterable collectFrom(ApplicationContext applicationContext) { + return Collections.emptySet(); + } + + /** + * Collects all schema objects of type {@link T} defined in the {@link GemFireCache}. + * + * @param gemfireCache {@link GemFireCache} from which to collect schema objects of type {@link T}. + * @return a {@link Set} of all schema objects of type {@link T} defined in the {@link GemFireCache}; + * returns an empty {@link Set} if no schema object of type {@link T} could be found. + * @see org.apache.geode.cache.GemFireCache + * @see java.lang.Iterable + */ + default Iterable collectFrom(GemFireCache gemfireCache) { + return Collections.emptySet(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectDefiner.java b/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectDefiner.java new file mode 100644 index 00000000..e7e44d47 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectDefiner.java @@ -0,0 +1,95 @@ +/* + * Copyright 2017 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.config.schema; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +/** + * The {@link SchemaObjectDefiner} interface defines a contract for implementing objects + * that can reverse engineer a schema object instance back into a definition of the schema object. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + * @since 2.0.0 + */ +public interface SchemaObjectDefiner { + + /** + * Returns a {@link Set} of {@link SchemaObjectType schema object types} definable by this definer. + * + * @return a {@link Set} of {@link SchemaObjectType schema object types} definable by this definer. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + * @see java.util.Set + */ + default Set getSchemaObjectTypes() { + return Collections.emptySet(); + } + + /** + * Determines whether this definer is able to define the given {@link Object schema object} instance. + * + * @param schemaObject {@link Object} to evaluate. + * @return a boolean value indicating whether this definer is able to define + * the given {@link Object schema object} instance. + * @see java.lang.Object#getClass() + * @see #canDefine(Class) + */ + default boolean canDefine(Object schemaObject) { + return Optional.ofNullable(schemaObject).map(Object::getClass).filter(this::canDefine).isPresent(); + } + + /** + * Determines whether this definer is able to define schema objects of the given {@link Class type}. + * + * @param schemaObjectType {@link Class type} of the {@link Object schema object} instance to evaluate. + * @return a boolean value indicating whether this definer is able to define {@link Object schema objects} + * of the given {@link Class type}. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType#from(Class) + * @see #canDefine(SchemaObjectType) + */ + default boolean canDefine(Class schemaObjectType) { + return canDefine(SchemaObjectType.from(schemaObjectType)); + } + + /** + * Determines whether this definer is able to define schema objects of the given + * {@link SchemaObjectType enumerated schema object type}. + * + * @param schemaObjectType {@link SchemaObjectType} to evaluate. + * @return a boolean value indicating whether this handler is able to handle schema objects + * of the given {@link SchemaObjectType enumerated schema object type}. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + */ + default boolean canDefine(SchemaObjectType schemaObjectType) { + return getSchemaObjectTypes().contains(schemaObjectType); + } + + /** + * Returns an {@link Optional} {@link SchemaObjectDefinition definition} for the given + * {@link Object schema object} instance. + * + * @param schemaObject {@link Object schema object} to define. + * @return an {@link Optional} {@link SchemaObjectDefinition definition} for the given + * {@link Object schema object} instance. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + */ + Optional define(Object schemaObject); + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectDefinition.java b/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectDefinition.java new file mode 100644 index 00000000..5942f904 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectDefinition.java @@ -0,0 +1,115 @@ +/* + * Copyright 2017 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.config.schema; + +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; + +import java.io.Serializable; +import java.util.Optional; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.query.Index; +import org.springframework.core.Ordered; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.util.StringUtils; + +/** + * {@link SchemaObjectDefinition} is an Abstract Data Type (ADT) encapsulating the definition of a single Apache Geode + * or Pivotal GemFire schema object (e.g. {@link Region} or {@link Index}). + * + * @author John Blum + * @see java.io.Serializable + * @see org.springframework.core.Ordered + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefiner + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + * @since 2.0.0 + */ +public abstract class SchemaObjectDefinition implements Serializable, Ordered { + + private final String name; + + /** + * Constructs a new instance of {@link SchemaObjectDefinition} initialized with the specified {@link String name}. + * + * @param name {@link String name} given to the GemFire/Geode schema object; must not be {@literal null}. + * @throws IllegalArgumentException if name is not specified. + */ + public SchemaObjectDefinition(String name) { + this.name = Optional.ofNullable(name).filter(StringUtils::hasText) + .orElseThrow(() -> newIllegalArgumentException("Name [%s] is required", name)); + } + + /** + * Returns the {@link String name} assigned to the schema object. + * + * @return the {@link String name} assigned to the schema object; name is never {@literal null}. + */ + public String getName() { + return this.name; + } + + /** + * Returns the {@link SchemaObjectType type} of schema object defined by this {@link SchemaObjectDefinition}. + * + * @return the {@link SchemaObjectType type} of schema object defined by this {@link SchemaObjectDefinition}. + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + */ + public abstract SchemaObjectType getType(); + + /** + * Creates an actual schema object from this {@link SchemaObjectDefinition}. + * + * @param gemfireAdminOperations {@link GemfireAdminOperations} used to create an actual schema object from this + * {@link SchemaObjectDefinition}. + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + */ + public void create(GemfireAdminOperations gemfireAdminOperations) { + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (!(obj instanceof SchemaObjectDefinition)) { + return false; + } + + SchemaObjectDefinition that = (SchemaObjectDefinition) obj; + + return this.getName().equals(that.getName()) + && this.getType().equals(that.getType()); + } + + @Override + public int hashCode() { + + int hashValue = 17; + + hashValue = 37 * hashValue + this.getName().hashCode(); + hashValue = 37 * hashValue + this.getType().hashCode(); + + return hashValue; + } + + @Override + public String toString() { + return String.format("%1$s[%2$s]", getType(), getName()); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectType.java b/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectType.java new file mode 100644 index 00000000..e45b0082 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/SchemaObjectType.java @@ -0,0 +1,126 @@ +/* + * Copyright 2017 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.config.schema; + +import static java.util.Arrays.stream; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.DiskStore; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.asyncqueue.AsyncEventQueue; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.Pool; +import org.apache.geode.cache.execute.Function; +import org.apache.geode.cache.lucene.LuceneIndex; +import org.apache.geode.cache.query.Index; +import org.apache.geode.cache.wan.GatewayReceiver; +import org.apache.geode.cache.wan.GatewaySender; + +/** + * {@link SchemaObjectType} defines an enumeration of all the types of Apache Geode or Pivotal GemFire schema objects + * (e.g. {@link Region}) that may possibly be handled by Spring Data Geode / Spring Data GemFire + * and that can be created remotely, from a client application. + * + * @author John Blum + * @see org.apache.geode.cache.asyncqueue.AsyncEventQueue + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.DiskStore + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.client.Pool + * @see org.apache.geode.cache.execute.Function + * @see org.apache.geode.cache.lucene.LuceneIndex + * @see org.apache.geode.cache.query.Index + * @see org.apache.geode.cache.wan.GatewayReceiver + * @see org.apache.geode.cache.wan.GatewaySender + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @since 2.0.0 + */ +public enum SchemaObjectType { + + ASYNC_EVENT_QUEUE(AsyncEventQueue.class), + CACHE(Cache.class), + CLIENT_CACHE(ClientCache.class), + DISK_STORE(DiskStore.class), + FUNCTION(Function.class), + GATEWAY_RECEIVER(GatewayReceiver.class), + GATEWAY_SENDER(GatewaySender.class), + INDEX(Index.class), + LUCENE_INDEX(LuceneIndex.class), + POOL(Pool.class), + REGION(Region.class), + UNKNOWN(Void.class); + + private final Class objectType; + + /** + * Constructs an instance of an {@link SchemaObjectType} enumerated value initialized with + * the actual GemFire {@link Class schema object instance type}. + * + * @param objectType actual {@link Class interface type} of the GemFire schema object instance. + * @see java.lang.Class + */ + SchemaObjectType(Class objectType) { + this.objectType = objectType; + } + + /** + * Null-safe factory method used to look up and resolve the corresponding {@link SchemaObjectType} + * given an instance of a GemFire schema object. + * + * For example, given an instance of {@link Region}, this factory method will return + * {@link SchemaObjectType#REGION}. + * + * @param obj actual instance of a GemFire schema object, e.g. reference to a {@link Region}. + * @return a corresponding {@link SchemaObjectType} for a given instance of a GemFire schema object. + * If the type cannot be determined, then {@link SchemaObjectType#UNKNOWN} is returned. + * @see #from(Class) + */ + public static SchemaObjectType from(Object obj) { + return stream(SchemaObjectType.values()) + .filter(it -> it.getObjectType().isInstance(obj)) + .findFirst().orElse(UNKNOWN); + } + + /** + * Null-safe factory method used to look up and resolve the corresponding {@link SchemaObjectType} + * given the type of GemFire schema object. + * + * For example, given the {@link Region} {@link Class interface} or any {@link Class sub-type} of {@link Region}, + * this factory method will return {@link SchemaObjectType#REGION}. + * + * @param type {@link Class type} of the GemFire schema object, e.g. the {@link Region} {@link Class interface}. + * @return a corresponding {@link SchemaObjectType} for a given {@link Class type }of a GemFire schema object. + * If the type cannot be determined, then {@link SchemaObjectType#UNKNOWN} is returned. + * @see #from(Object) + */ + public static SchemaObjectType from(Class type) { + return stream(SchemaObjectType.values()) + .filter(it -> type != null && it.getObjectType().isAssignableFrom(type)) + .findFirst().orElse(UNKNOWN); + } + + /** + * Returns the {@link Class class type} of the GemFire schema object represented by this enumerated value. + * + * @return the {@link Class class type} of the GemFire schema object represented by this enumerated value. + * @see java.lang.Class + */ + public Class getObjectType() { + return this.objectType; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/definitions/IndexDefinition.java b/src/main/java/org/springframework/data/gemfire/config/schema/definitions/IndexDefinition.java new file mode 100644 index 00000000..f468d8b6 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/definitions/IndexDefinition.java @@ -0,0 +1,232 @@ +/* + * Copyright 2017 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.config.schema.definitions; + +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Optional; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.query.Index; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefinition; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.data.gemfire.domain.support.AbstractIndexSupport; +import org.springframework.util.StringUtils; + +/** + * {@link IndexDefinition} is an Abstract Data Type (ADT) encapsulating the configuration meta-data used to define + * a new Apache Geode / Pivotal GemFire {@link Region} {@link Index}. + * + * @author John Blum + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.query.Index + * @see org.springframework.data.gemfire.IndexType + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + * @since 2.0.0 + */ +public class IndexDefinition extends SchemaObjectDefinition { + + protected static final int ORDER = RegionDefinition.ORDER + 1; + + /** + * Factory method used to construct a new instance of {@link IndexDefinition} defined from the given {@link Index}. + * + * @param index {@link Index} on which the new {@link IndexDefinition} will be defined; + * must not be {@literal null}. + * @return a new instance of {@link IndexDefinition} defined from the given {@link Index}. + * @throws IllegalArgumentException if {@link Index} is {@literal null}. + * @see org.apache.geode.cache.query.Index + */ + public static IndexDefinition from(Index index) { + return new IndexDefinition(index); + } + + private transient Index index; + + private IndexType indexType; + + private String expression; + private String fromClause; + private String name; + + /** + * Constructs a new instance of {@link IndexDefinition} defined with the given {@link Index}. + * + * @param index {@link Index} on which this definition is defined; must not be {@literal null}. + * @throws IllegalArgumentException if {@link Index} is {@literal null}. + * @see org.apache.geode.cache.query.Index + */ + protected IndexDefinition(Index index) { + + super(Optional.ofNullable(index).map(Index::getName) + .orElseThrow(() -> newIllegalArgumentException("Index is required"))); + + this.index = index; + } + + /** + * Returns a reference to the {@link Index} on which this definition is defined. + * + * @return a reference to the {@link Index} on which this definition is defined. + * @see org.apache.geode.cache.query.Index + */ + protected Index getIndex() { + return this.index; + } + + @Override + public String getName() { + return Optional.ofNullable(this.name).filter(StringUtils::hasText).orElseGet(super::getName); + } + + public String getExpression() { + return Optional.ofNullable(this.expression).filter(StringUtils::hasText) + .orElseGet(this.index::getIndexedExpression); + } + + public String getFromClause() { + return Optional.ofNullable(this.fromClause).filter(StringUtils::hasText) + .orElseGet(this.index::getFromClause); + } + + public IndexType getIndexType() { + return Optional.ofNullable(this.indexType).orElseGet(() -> IndexType.valueOf(this.index.getType())); + } + + /** + * Get the order value of this object. + * + * @return the order value of this object. + * @see org.springframework.core.Ordered + */ + @Override + public int getOrder() { + return ORDER; + } + + @Override + public SchemaObjectType getType() { + return SchemaObjectType.INDEX; + } + + @Override + public void create(GemfireAdminOperations gemfireAdminOperations) { + gemfireAdminOperations.createIndex(this); + } + + @SuppressWarnings("deprecation") + public IndexDefinition as(org.apache.geode.cache.query.IndexType gemfireGeodeIndexType) { + return as(IndexType.valueOf(gemfireGeodeIndexType)); + } + + public IndexDefinition as(IndexType indexType) { + this.indexType = indexType; + return this; + } + + public IndexDefinition having(String expression) { + + this.expression = Optional.ofNullable(expression).filter(StringUtils::hasText) + .orElseThrow(() -> newIllegalArgumentException("Expression is required")); + + return this; + } + + public IndexDefinition on(Region region) { + return on(Optional.ofNullable(region).map(Region::getFullPath) + .orElseThrow(() -> newIllegalArgumentException("Region is required"))); + } + + public IndexDefinition on(String fromClause) { + + this.fromClause = Optional.ofNullable(fromClause).filter(StringUtils::hasText) + .orElseThrow(() -> newIllegalArgumentException("From Clause is required")); + + return this; + } + + public IndexDefinition with(String name) { + this.name = name; + return this; + } + + private void writeObject(ObjectOutputStream outputStream) throws IOException { + outputStream.writeUTF(getName()); + outputStream.writeUTF(getExpression()); + outputStream.writeUTF(getFromClause()); + outputStream.writeObject(getIndexType()); + } + + private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException { + + String name = inputStream.readUTF(); + String expression = inputStream.readUTF(); + String fromClause = inputStream.readUTF(); + + IndexType indexType = (IndexType) inputStream.readObject(); + + this.index = IndexWrapper.from(name, expression, fromClause, indexType); + } + + protected static final class IndexWrapper extends AbstractIndexSupport { + + private IndexType indexType; + + private final String name; + private final String expression; + private final String fromClause; + + protected static Index from(String name, String expression, String fromClause, IndexType indexType) { + return new IndexWrapper(name, expression, fromClause, indexType); + } + + protected IndexWrapper(String name, String expression, String fromClause, IndexType indexType) { + this.name = name; + this.expression = expression; + this.fromClause = fromClause; + this.indexType = indexType; + } + + @Override + public String getFromClause() { + return this.fromClause; + } + + @Override + public String getIndexedExpression() { + return this.expression; + } + + @Override + public String getName() { + return this.name; + } + + @Override + @SuppressWarnings("deprecation") + public org.apache.geode.cache.query.IndexType getType() { + return Optional.ofNullable(this.indexType).map(IndexType::getGemfireIndexType).orElse(null); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/definitions/RegionDefinition.java b/src/main/java/org/springframework/data/gemfire/config/schema/definitions/RegionDefinition.java new file mode 100644 index 00000000..9b7174ea --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/definitions/RegionDefinition.java @@ -0,0 +1,146 @@ +/* + * Copyright 2017 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.config.schema.definitions; + +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Optional; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefinition; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.util.StringUtils; + +/** + * {@link RegionDefinition} is an Abstract Data Type (ADT) encapsulating the configuration meta-data used to + * define a new Apache Geode / Pivotal GemFire cache {@link Region}. + * + * @author John Blum + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.RegionShortcut + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + * @since 2.0.0 + */ +public class RegionDefinition extends SchemaObjectDefinition { + + protected static final int ORDER = 1; + + public static final RegionShortcut DEFAULT_REGION_SHORTCUT = RegionShortcut.PARTITION; + + /** + * Factory method used to construct a new instance of {@link RegionDefinition} defined from + * the given {@link Region}. + * + * @param region {@link Region} from which the new definition will be defined. + * @return a new instance of {@link RegionDefinition} defined from the given {@link Region}. + * @throws IllegalArgumentException if {@link Region} is {@literal null}. + * @see org.apache.geode.cache.Region + * @see #RegionDefinition(Region) + */ + public static RegionDefinition from(Region region) { + return Optional.ofNullable(region).map(RegionDefinition::new) + .orElseThrow(() -> newIllegalArgumentException("Region is required")); + } + + private final transient Region region; + + private RegionShortcut regionShortcut; + + private String name; + + /** + * Constructs a new instance of {@link RegionDefinition} defined with the given {@link Region}. + * + * @param region {@link Region} on which this definition is defined; must not be {@literal null}. + * @throws IllegalArgumentException if {@link Region} is {@literal null}. + * @see org.apache.geode.cache.Region + */ + protected RegionDefinition(Region region) { + + super(Optional.ofNullable(region).map(Region::getName) + .orElseThrow(() -> newIllegalArgumentException("Region is required"))); + + this.region = region; + } + + /** + * Get the order value of this object. + * + * @return the order value of this object. + * @see org.springframework.core.Ordered + */ + @Override + public int getOrder() { + return ORDER; + } + + /** + * Returns a reference to the {@link Region} from which this definition is defined. + * + * @return a reference to the {@link Region} from which this definition is defined. + * @see org.apache.geode.cache.Region + */ + protected Region getRegion() { + return this.region; + } + + @Override + public String getName() { + return Optional.ofNullable(this.name).filter(StringUtils::hasText).orElseGet(super::getName); + } + + public RegionShortcut getRegionShortcut() { + return Optional.ofNullable(this.regionShortcut).orElse(DEFAULT_REGION_SHORTCUT); + } + + @Override + public SchemaObjectType getType() { + return SchemaObjectType.REGION; + } + + @Override + public void create(GemfireAdminOperations gemfireAdminOperations) { + gemfireAdminOperations.createRegion(this); + } + + public RegionDefinition having(RegionShortcut regionShortcut) { + this.regionShortcut = regionShortcut; + return this; + } + + public RegionDefinition with(String name) { + this.name = name; + return this; + } + + private void writeObject(ObjectOutputStream outputStream) throws IOException { + outputStream.writeUTF(getName()); + outputStream.writeObject(getRegionShortcut()); + } + + private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException { + this.name = inputStream.readUTF(); + this.regionShortcut = (RegionShortcut) inputStream.readObject(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/support/ClientRegionCollector.java b/src/main/java/org/springframework/data/gemfire/config/schema/support/ClientRegionCollector.java new file mode 100644 index 00000000..dca0dc66 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/support/ClientRegionCollector.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeSet; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.util.RegionUtils; + +/** + * The {@link ClientRegionCollector} class is an extension of the {@link RegionCollector} which applies additional + * filtering to find only client {@link Region Regions} in a given context. + * + * @author John Blum + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.springframework.context.ApplicationContext + * @see org.springframework.data.gemfire.config.schema.SchemaObjectCollector + * @since 2.0.0 + */ +public class ClientRegionCollector extends RegionCollector { + + @Override + public Set collectFrom(ApplicationContext applicationContext) { + return onlyClientRegions(super.collectFrom(applicationContext)); + } + + @Override + public Set collectFrom(GemFireCache gemfireCache) { + return onlyClientRegions(super.collectFrom(gemfireCache)); + } + + private Set onlyClientRegions(Set regions) { + return nullSafeSet(regions).stream().filter(RegionUtils::isClient).collect(Collectors.toSet()); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectCollector.java b/src/main/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectCollector.java new file mode 100644 index 00000000..182eb733 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectCollector.java @@ -0,0 +1,93 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static java.util.stream.StreamSupport.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.geode.cache.GemFireCache; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.config.schema.SchemaObjectCollector; +import org.springframework.lang.Nullable; + +/** + * The {@link ComposableSchemaObjectCollector} class is a {@link SchemaObjectCollector} implementation composed of + * multiple {@link SchemaObjectCollector} objects wrapped in a facade and treated like a single + * {@link SchemaObjectCollector} using the Composite Software Design Pattern. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.schema.SchemaObjectCollector + * @since 2.0.0 + */ +public final class ComposableSchemaObjectCollector + implements SchemaObjectCollector, Iterable> { + + private final List> schemaObjectCollectors; + + @Nullable + @SuppressWarnings("unchecked") + public static SchemaObjectCollector compose(SchemaObjectCollector... schemaObjectCollectors) { + return compose(Arrays.asList(nullSafeArray(schemaObjectCollectors, SchemaObjectCollector.class))); + } + + @Nullable + public static SchemaObjectCollector compose(Iterable> schemaObjectCollectors) { + + List> schemaObjectCollectorList = + stream(nullSafeIterable(schemaObjectCollectors).spliterator(), false) + .filter(Objects::nonNull).collect(Collectors.toList()); + + return (schemaObjectCollectorList.isEmpty() ? null + : (schemaObjectCollectorList.size() == 1 ? schemaObjectCollectorList.iterator().next() + : new ComposableSchemaObjectCollector(schemaObjectCollectorList))); + } + + private ComposableSchemaObjectCollector(List> schemaObjectCollectors) { + this.schemaObjectCollectors = Collections.unmodifiableList(schemaObjectCollectors); + } + + @Override + public Iterable collectFrom(ApplicationContext applicationContext) { + return collectFrom(collector -> collector.collectFrom(applicationContext)); + } + + @Override + public Iterable collectFrom(GemFireCache gemfireCache) { + return collectFrom(collector -> collector.collectFrom(gemfireCache)); + } + + private Iterable collectFrom(Function, Iterable> schemaObjectSource) { + + return this.schemaObjectCollectors.stream() + .flatMap(collector -> stream(schemaObjectSource.apply(collector).spliterator(), false)) + .collect(Collectors.toList()); + } + + @Override + public Iterator> iterator() { + return Collections.unmodifiableList(this.schemaObjectCollectors).iterator(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectDefiner.java b/src/main/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectDefiner.java new file mode 100644 index 00000000..4d63db30 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectDefiner.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static java.util.stream.StreamSupport.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.data.gemfire.config.schema.SchemaObjectDefiner; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefinition; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.lang.Nullable; + +/** + * {@link ComposableSchemaObjectDefiner} is an implementation of {@link SchemaObjectDefiner} + * as well as a composition of {@link SchemaObjectDefiner SchemaObjectInstanceHandlers} composed + * using the Composite Software Design Pattern. + * + * @author John Blum + * @see java.lang.Iterable + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefiner + * @since 2.0.0 + */ +public final class ComposableSchemaObjectDefiner + implements SchemaObjectDefiner, Iterable { + + @Nullable + public static SchemaObjectDefiner compose(SchemaObjectDefiner... schemaObjectDefiners) { + return compose(Arrays.asList(nullSafeArray(schemaObjectDefiners, SchemaObjectDefiner.class))); + } + + @Nullable + public static SchemaObjectDefiner compose(Iterable schemaObjectDefiners) { + + Set schemaObjectDefinerSet = + stream(nullSafeIterable(schemaObjectDefiners).spliterator(), false) + .filter(Objects::nonNull).collect(Collectors.toSet()); + + return (schemaObjectDefinerSet.isEmpty() ? null + : (schemaObjectDefinerSet.size() == 1 ? schemaObjectDefinerSet.iterator().next() + : new ComposableSchemaObjectDefiner(schemaObjectDefinerSet))); + } + + private final Set schemaObjectDefiners; + + /** + * Constructs a new instance of {@link ComposableSchemaObjectDefiner} initialized and compose of + * the given {@link Set} of {@link SchemaObjectDefiner SchemaObjectInstanceHandlers}. + * + * @param schemaObjectDefiners {@link Set} of {@link SchemaObjectDefiner SchemaObjectInstanceHandlers} + * from which this {@link ComposableSchemaObjectDefiner} is composed. + * @see SchemaObjectDefiner + */ + private ComposableSchemaObjectDefiner(Set schemaObjectDefiners) { + this.schemaObjectDefiners = Collections.unmodifiableSet(schemaObjectDefiners); + } + + @Override + public Set getSchemaObjectTypes() { + return this.schemaObjectDefiners.stream() + .flatMap(it -> it.getSchemaObjectTypes().stream()) + .collect(Collectors.toSet()); + } + + @Override + public Optional define(Object schemaObject) { + + return this.schemaObjectDefiners.stream() + .filter(it -> it.canDefine(schemaObject)).findAny() + .map(it -> it.define(schemaObject).orElse(null)); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.schemaObjectDefiners).iterator(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/support/IndexCollector.java b/src/main/java/org/springframework/data/gemfire/config/schema/support/IndexCollector.java new file mode 100644 index 00000000..0d049aa8 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/support/IndexCollector.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 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.config.schema.support; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.query.Index; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.config.schema.SchemaObjectCollector; + +/** + * The {@link IndexCollector} class is an implementation of the {@link SchemaObjectCollector} that is capable of + * inspecting a context and finding all {@link Index} schema object instances that have been declared in that context. + * + * @author John Blum + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.query.Index + * @see org.springframework.context.ApplicationContext + * @see org.springframework.data.gemfire.config.schema.SchemaObjectCollector + * @since 2.0.0 + */ +public class IndexCollector implements SchemaObjectCollector { + + @Override + public Set collectFrom(ApplicationContext applicationContext) { + return applicationContext.getBeansOfType(Index.class).values().stream().collect(Collectors.toSet()); + } + + @Override + public Set collectFrom(GemFireCache gemfireCache) { + return gemfireCache.getQueryService().getIndexes().stream().collect(Collectors.toSet()); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/support/IndexDefiner.java b/src/main/java/org/springframework/data/gemfire/config/schema/support/IndexDefiner.java new file mode 100644 index 00000000..837d61db --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/support/IndexDefiner.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; + +import java.util.Optional; +import java.util.Set; + +import org.apache.geode.cache.query.Index; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefiner; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; + +/** + * The {@link {{@link IndexDefiner }} class is responsible for defining an {@link Index} given a reference to + * an {@link Index} instance. + * + * @author John Blum + * @see org.apache.geode.cache.query.Index + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefiner + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + * @see org.springframework.data.gemfire.config.schema.definitions.IndexDefinition + * @since 2.0.0 + */ +public class IndexDefiner implements SchemaObjectDefiner { + + @Override + public Set getSchemaObjectTypes() { + return asSet(SchemaObjectType.INDEX); + } + + @Override + public Optional define(Object schemaObject) { + return Optional.ofNullable(schemaObject).filter(this::canDefine).map(it -> IndexDefinition.from((Index) it)); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/support/RegionCollector.java b/src/main/java/org/springframework/data/gemfire/config/schema/support/RegionCollector.java new file mode 100644 index 00000000..1bb821ca --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/support/RegionCollector.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 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.config.schema.support; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.config.schema.SchemaObjectCollector; + +/** + * The {@link RegionCollector} class is an implementation of the {@link SchemaObjectCollector} that is capable of + * inspecting a context and finding all {@link Region} schema object instances that have been declared in that context. + * + * @author John Blum + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.springframework.context.ApplicationContext + * @see org.springframework.data.gemfire.config.schema.SchemaObjectCollector + * @since 2.0.0 + */ +public class RegionCollector implements SchemaObjectCollector { + + @Override + public Set collectFrom(ApplicationContext applicationContext) { + return applicationContext.getBeansOfType(Region.class).values().stream().collect(Collectors.toSet()); + } + + @Override + public Set collectFrom(GemFireCache gemfireCache) { + return gemfireCache.rootRegions().stream().collect(Collectors.toSet()); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/schema/support/RegionDefiner.java b/src/main/java/org/springframework/data/gemfire/config/schema/support/RegionDefiner.java new file mode 100644 index 00000000..19ca374f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/schema/support/RegionDefiner.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; + +import java.util.Optional; +import java.util.Set; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefiner; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; +import org.springframework.data.gemfire.util.RegionUtils; + +/** + * The {@link {RegionDefiner} class is responsible for defining a {@link Region} + * given a reference to a {@link Region} instance. + * + * @author John Blum + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.RegionAttributes + * @see org.apache.geode.cache.RegionShortcut + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefiner + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + * @see org.springframework.data.gemfire.config.schema.definitions.RegionDefinition + * @since 2.0.0 + */ +public class RegionDefiner implements SchemaObjectDefiner { + + private final RegionShortcut regionShortcut; + + public RegionDefiner() { + this(RegionDefinition.DEFAULT_REGION_SHORTCUT); + } + + public RegionDefiner(RegionShortcut regionShortcut) { + this.regionShortcut = regionShortcut; + } + + protected RegionShortcut getRegionShortcut() { + return Optional.ofNullable(this.regionShortcut).orElse(RegionDefinition.DEFAULT_REGION_SHORTCUT); + } + + @Override + public Set getSchemaObjectTypes() { + return asSet(SchemaObjectType.REGION); + } + + @Override + public Optional define(Object schemaObject) { + + return Optional.ofNullable(schemaObject) + .filter(this::canDefine) + .map(it -> (Region) it) + .filter(RegionUtils::isClient) + .map(it -> RegionDefinition.from(it).having(getRegionShortcut())); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/domain/support/AbstractIndexSupport.java b/src/main/java/org/springframework/data/gemfire/domain/support/AbstractIndexSupport.java new file mode 100644 index 00000000..04039b7f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/domain/support/AbstractIndexSupport.java @@ -0,0 +1,86 @@ +/* + * Copyright 2017 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.domain.support; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.query.Index; +import org.apache.geode.cache.query.IndexStatistics; +import org.apache.geode.cache.query.IndexType; + +/** + * {@link AbstractIndexSupport} is an abstract base class supporting the implementation + * of the Pivotal GemFire / Apache Geode {@link Index} interface. + * + * @author John Blum + * @see org.apache.geode.cache.query.Index + * @since 2.0.0 + */ +public abstract class AbstractIndexSupport implements Index { + + private static final String NOT_IMPLEMENTED = "Not Implemented"; + + @Override + public String getCanonicalizedFromClause() { + return getFromClause(); + } + + @Override + public String getCanonicalizedIndexedExpression() { + return getIndexedExpression(); + } + + @Override + public String getCanonicalizedProjectionAttributes() { + return getProjectionAttributes(); + } + + @Override + public String getFromClause() { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + @Override + public String getIndexedExpression() { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + @Override + public String getName() { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + @Override + public String getProjectionAttributes() { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + @Override + public Region getRegion() { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + @Override + public IndexStatistics getStatistics() { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } + + @Override + @SuppressWarnings("deprecation") + public IndexType getType() { + throw new UnsupportedOperationException(NOT_IMPLEMENTED); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/function/GemfireFunctionUtils.java b/src/main/java/org/springframework/data/gemfire/function/GemfireFunctionUtils.java index 89c2bca7..fdded92b 100644 --- a/src/main/java/org/springframework/data/gemfire/function/GemfireFunctionUtils.java +++ b/src/main/java/org/springframework/data/gemfire/function/GemfireFunctionUtils.java @@ -13,80 +13,269 @@ package org.springframework.data.gemfire.function; +import static java.util.Arrays.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; + import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.geode.cache.execute.Function; import org.apache.geode.cache.execute.FunctionService; +import org.springframework.beans.BeanUtils; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.gemfire.function.annotation.GemfireFunction; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** + * Utility class for registering a POJO as a GemFire/Geode {@link Function}. + * * @author David Turanski + * @author John Blum + * @see org.apache.geode.cache.execute.Function + * @see org.apache.geode.cache.execute.FunctionService + * @see org.springframework.data.gemfire.function.annotation.GemfireFunction + * @since 1.2.0 */ public abstract class GemfireFunctionUtils { private static Log log = LogFactory.getLog(GemfireFunctionUtils.class); /** - * Wrap a target object and method in a GemFire Function and register the function to the {@link FunctionService} + * Determines whether the given {@link Method} is a POJO, {@link GemfireFunction} annotated {@link Method}. * - * @param target the target object - * @param method the method bound to the function - * @param attributes function attributes - * @param overwrite if true, will replace the existing function + * @param method {@link Method} to evaluate. + * @return a boolean value indicating whether the {@link Method} on a POJO represents a SDG {@link GemfireFunction}. + * @see org.springframework.data.gemfire.function.annotation.GemfireFunction + * @see java.lang.reflect.Method */ - public static void registerFunctionForPojoMethod(Object target, Method method, Map attributes, - boolean overwrite) { + public static boolean isGemfireFunction(Method method) { + return (method != null && method.isAnnotationPresent(GemfireFunction.class)); + } - String id = attributes.containsKey("id") ? (String) attributes.get("id") : ""; + private static boolean isMatchingGemfireFunction(Method method, String functionId) { + return getGemfireFunctionId(method).filter(methodFunctionId -> + ObjectUtils.nullSafeEquals(methodFunctionId, functionId)).isPresent(); + } + + private static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, + Class annotationType) { + + return AnnotationAttributes.fromMap( + AnnotationUtils.getAnnotationAttributes(element, element.getAnnotation(annotationType))); + } + + /** + * Null-safe operation used to determine the GemFire/Geode {@link Function#getId()} Function ID} + * of a given {@link GemfireFunction} annotated POJO {@link Method}. + * + * If the {@link Method} is not {@literal null} and annotated with {@link GemfireFunction} then this method + * tries to determine the {@link Function#getId()} from the {@link GemfireFunction#id()} attribute. + * + * If the {@link GemfireFunction#id()} attribute was not set, then the {@link Method#getName()} + * is returned as the {@link Function#getId() Function ID}. + * + * @param method {@link GemfireFunction} annotated POJO {@link Method} containing the implementation + * for a GemFire/Geode {@link Function}. + * @return the GemFire/Geode Function ID of the given {@link Method}, or an empty {@link Optional} + * if the {@link Method} is not a GemFire/Geode {@link Function}. + * @see org.springframework.data.gemfire.function.annotation.GemfireFunction + * @see java.lang.reflect.Method + * @see #isGemfireFunction(Method) + */ + private static Optional getGemfireFunctionId(Method method) { + + return Optional.ofNullable(method) + .filter(GemfireFunctionUtils::isGemfireFunction) + .map(it -> + Optional.of(it.getAnnotation(GemfireFunction.class)) + .map(annotation -> getAnnotationAttributes(it, annotation.getClass())) + .filter(annotationAttributes -> annotationAttributes.containsKey("id")) + .map(annotationAttributes -> annotationAttributes.getString("id")) + .filter(StringUtils::hasText) + .orElseGet(() -> it.getName()) + ); + } + + private static Object constructInstance(Class type, Object... constructorArguments) { + + return org.springframework.data.util.ReflectionUtils.findConstructor(type, constructorArguments) + .map(constructor -> BeanUtils.instantiateClass(constructor, constructorArguments)) + .orElseThrow(() -> newIllegalArgumentException( + "No suitable constructor was found for type [%s] having parameters [%s]", type.getName(), + stream(nullSafeArray(constructorArguments, Object.class)).map(ObjectUtils::nullSafeClassName) + .collect(Collectors.toList()))); + } + + private static String nullSafeName(Method method) { + + return Optional.ofNullable(method) + .map(it -> String.format("%s.%s", method.getDeclaringClass().getName(), method.getName())) + .orElse(null); + } + + /** + * Bind a {@link Method method} with the given {@link Function#getId() Function ID} on an object + * of the given {@link Class type} as a {@link Function} and register it with the {@link FunctionService}. + * + * @param type {@link Class target type} to evaluate; must not be {@literal null}. + * @param functionId {@link String} containing the {@link Function#getId()} identifying the {@link Method} + * on the {@link Class target type} to bind as a {@link Function}. + * @throws IllegalArgumentException if {@link Class type} is {@literal null}. + * @see #registerFunctionForPojoMethod(Object, Method, AnnotationAttributes, boolean) + * @see #isMatchingGemfireFunction(Method, String) + */ + @SuppressWarnings("unused") + public static void registerFunctionForPojoMethod(Class type, String functionId) { + + Assert.notNull(type, () -> String.format("Class type of POJO containing %s(s) is required", + GemfireFunction.class.getName())); + + ReflectionUtils.doWithMethods(type, + method -> registerFunctionForPojoMethod(constructInstance(type), method, + getAnnotationAttributes(method, GemfireFunction.class), true), + method -> isMatchingGemfireFunction(method, functionId)); + + } + + /** + * Bind a {@link Method method} with the given {@link Function#getId() Function ID} on the given {@link Object target} + * as a {@link Function} and register it with the {@link FunctionService}. + * + * @param target {@link Object target object} to evaluate; must not be {@literal null}. + * @param functionId {@link String} containing the {@link Function#getId()} identifying the {@link Method} + * on the {@link Object target object} to bind as a {@link Function}. + * @throws IllegalArgumentException if {@link Object target} is {@literal null}. + * @see #registerFunctionForPojoMethod(Object, Method, AnnotationAttributes, boolean) + * @see #isMatchingGemfireFunction(Method, String) + */ + public static void registerFunctionForPojoMethod(Object target, String functionId) { + + Assert.notNull(target, "Target object is required"); + + ReflectionUtils.doWithMethods(target.getClass(), + method -> registerFunctionForPojoMethod(target, method, + getAnnotationAttributes(method, GemfireFunction.class), true), + method -> isMatchingGemfireFunction(method, functionId)); + } + + /** + * Binds the given {@link Method method} on the given {@link Object target} as a {@link Function} + * and registers it with the {@link FunctionService}. + * + * @param target {@link Object target object} to evaluate; must not be {@literal null}. + * @param method {@link Method} on {@link Object target} bound as a {@link Function}. + * @param overwrite if {@literal true}, will replace any existing {@link Function} + * having the same {@link Function#getId() ID}. + * @throws IllegalArgumentException if {@link Object target} is {@literal null} or the given {@link Method} + * is not a {@link GemfireFunction}. + * @see #registerFunctionForPojoMethod(Object, Method, AnnotationAttributes, boolean) + * @see #isGemfireFunction(Method) + */ + public static void registerFunctionForPojoMethod(Object target, Method method, boolean overwrite) { + + Assert.notNull(target, "Target object is required"); + + Assert.isTrue(isGemfireFunction(method), () -> String.format("Method [%s] must be a %s", + nullSafeName(method), GemfireFunction.class.getName())); + + registerFunctionForPojoMethod(target, method, getAnnotationAttributes(method, GemfireFunction.class), overwrite); + + } + + /** + * Wrap the {@link Object target object} and {@link Method method} in a GemFire/Geode {@link Function} + * and register the {@link Function} with the {@link FunctionService}. + * + * @param target {@link Object target object}. + * @param method {@link Method} bound to a {@link Function}. + * @param gemfireFunctionAttributes {@link GemfireFunction} annotation {@link Map attributes}. + * @param overwrite if {@literal true}, will replace any existing {@link Function} having the same ID. + * @deprecated use {@link #registerFunctionForPojoMethod(Object, Method, AnnotationAttributes, boolean)} instead. + */ + @Deprecated + public static void registerFunctionForPojoMethod(Object target, Method method, + Map gemfireFunctionAttributes, boolean overwrite) { + + registerFunctionForPojoMethod(target, method, AnnotationAttributes.fromMap(gemfireFunctionAttributes), overwrite); + } + + /** + * Wrap the {@link Object target object} and {@link Method method} in a GemFire/Geode {@link Function} + * and register the {@link Function} with the {@link FunctionService}. + * + * @param target {@link Object target object}. + * @param method {@link Method} bound to a {@link Function}. + * @param gemfireFunctionAttributes {@link GemfireFunction} {@link AnnotationAttributes annotation attributes}. + * @param overwrite if {@literal true}, will replace any existing {@link Function} having the same ID. + */ + public static void registerFunctionForPojoMethod(Object target, Method method, + AnnotationAttributes gemfireFunctionAttributes, boolean overwrite) { + + String id = gemfireFunctionAttributes.containsKey("id") + ? gemfireFunctionAttributes.getString("id") : ""; PojoFunctionWrapper function = new PojoFunctionWrapper(target, method, id); - if (attributes.containsKey("HA")) { - function.setHA((Boolean) attributes.get("HA")); - } + if (gemfireFunctionAttributes.containsKey("batchSize")) { - if (attributes.containsKey("optimizeForWrite")) { - function.setOptimizeForWrite((Boolean) attributes.get("optimizeForWrite")); - } + int batchSize = gemfireFunctionAttributes.getNumber("batchSize"); + + Assert.isTrue(batchSize >= 0, + String.format("batchSize [%1$d] specified on [%2$s.%3$s] must be a non-negative value", + batchSize, target.getClass().getName(), method.getName())); - if (attributes.containsKey("batchSize")) { - int batchSize = (Integer) attributes.get("batchSize"); - Assert.isTrue(batchSize >= 0, String.format("batchSize must be a non-negative value %1$s.%2$s", - target.getClass().getName(), method.getName())); function.setBatchSize(batchSize); } - if (attributes.containsKey("hasResult")) { - // only set if true TODO figure out why??? - if (Boolean.TRUE.equals(attributes.get("hasResult"))) { + if (gemfireFunctionAttributes.containsKey("HA")) { + function.setHA(gemfireFunctionAttributes.getBoolean("HA")); + } + + if (gemfireFunctionAttributes.containsKey("hasResult")) { + if (Boolean.TRUE.equals(gemfireFunctionAttributes.getBoolean("hasResult"))) { function.setHasResult(true); } } + if (gemfireFunctionAttributes.containsKey("optimizeForWrite")) { + function.setOptimizeForWrite(gemfireFunctionAttributes.getBoolean("optimizeForWrite")); + } + if (FunctionService.isRegistered(function.getId())) { if (overwrite) { + if (log.isDebugEnabled()) { - log.debug("unregistering function definition " + function.getId()); + log.debug(String.format("Unregistering Function [%s]", function.getId())); } + FunctionService.unregisterFunction(function.getId()); } } if (!FunctionService.isRegistered(function.getId())) { + FunctionService.registerFunction(function); + if (log.isDebugEnabled()) { - log.debug("registered function " + function.getId()); + log.debug(String.format("Registered Function [%s]", function.getId())); } } else { if (log.isDebugEnabled()) { - log.debug("function " + function.getId() + "is already registered"); + log.debug(String.format("Function [%s] is already registered", function.getId())); } } } @@ -107,16 +296,19 @@ public abstract class GemfireFunctionUtils { Annotation[][] parameterAnnotations = method.getParameterAnnotations(); if (parameterAnnotations.length > 0) { + Class[] parameterTypes = method.getParameterTypes(); List> requiredTypesList = Arrays.asList(requiredTypes); for (int index = 0; index < parameterAnnotations.length; index++) { + Annotation[] annotations = parameterAnnotations[index]; if (annotations.length > 0) { for (Annotation annotation : annotations) { if (annotation.annotationType().equals(targetAnnotationType)) { + Assert.state(position < 0, String.format( "Method %s signature cannot contain more than one parameter annotated with type %s", method.getName(), targetAnnotationType.getName())); @@ -144,5 +336,4 @@ public abstract class GemfireFunctionUtils { return position; } - } diff --git a/src/main/java/org/springframework/data/gemfire/function/PojoFunctionWrapper.java b/src/main/java/org/springframework/data/gemfire/function/PojoFunctionWrapper.java index 4a975079..a527cd71 100644 --- a/src/main/java/org/springframework/data/gemfire/function/PojoFunctionWrapper.java +++ b/src/main/java/org/springframework/data/gemfire/function/PojoFunctionWrapper.java @@ -16,6 +16,7 @@ import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.geode.cache.Region; import org.apache.geode.cache.execute.Function; import org.apache.geode.cache.execute.FunctionContext; import org.apache.geode.cache.execute.ResultSender; @@ -24,16 +25,22 @@ import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** - * Invokes a POJO's given method as a Gemfire remote function. - * If the POJO has a constructor that takes a Map, and the function context is Region, the - * region will be injected. The delegate class name, the method name, and the method arguments - * are part of a remote function invocation, therefore all arguments must be serializable - * or an alternate serialization method must be used. - * The delegate class must be the class path of the remote cache(s) - * @author David Turanski + * Invokes a given {@link Object POJO} {@link Method} as a (remote) GemFire/Geode {@link Function}. * + * If the {@link Object POJO} has a constructor that takes a {@link java.util.Map}, and the {@link Function} context + * is a {@link Region}, the {@link Region} will be injected. + * + * The delegate {@link Class#getName() class name}, the {@link Method#getName() method name}, + * and {@link Method} arguments are part of the {@link Function} invocation, therefore all arguments + * must be {@link java.io.Serializable} or an alternate serialization strategy must be used. + * + * The delegate {@link Class} must be on the class path of the remote cache(s). + * + * @author David Turanski + * @author John Blum + * @see org.apache.geode.cache.execute.Function + * @since 1.2.0 */ - @SuppressWarnings("serial") public class PojoFunctionWrapper implements Function { @@ -59,7 +66,7 @@ public class PojoFunctionWrapper implements Function { this.method = method; this.id = (StringUtils.hasText(id) ? id : method.getName()); this.HA = false; - this.hasResult = !(method.getReturnType().equals(void.class)); + this.hasResult = !method.getReturnType().equals(void.class); this.optimizeForWrite = false; } @@ -100,7 +107,9 @@ public class PojoFunctionWrapper implements Function { } @Override + @SuppressWarnings("unchecked") public void execute(final FunctionContext functionContext) { + Object[] args = this.functionArgumentResolver.resolveFunctionArguments(functionContext); Object result = invokeTargetMethod(args); @@ -111,19 +120,22 @@ public class PojoFunctionWrapper implements Function { } protected final Object invokeTargetMethod(Object[] args) { + if (logger.isDebugEnabled()) { - logger.debug(String.format("about to invoke method %s on class %s as function %s", method.getName(), - target.getClass().getName(), this.id)); + + logger.debug(String.format("About to invoke method [%s] on class [%s] as Function [%s]", + this.method.getName(), this.target.getClass().getName(), getId())); for (Object arg : args) { - logger.debug("arg:" + arg.getClass().getName() + " " + arg.toString()); + logger.debug(String.format("Argument of type [%s] is [%s]", arg.getClass().getName(), arg.toString())); } } - return ReflectionUtils.invokeMethod(method, target, (Object[]) args); + return ReflectionUtils.invokeMethod(this.method, this.target, (Object[]) args); } private void sendResults(ResultSender resultSender, Object result) { + if (result == null) { resultSender.lastResult(null); } @@ -139,5 +151,4 @@ public class PojoFunctionWrapper implements Function { } } } - } diff --git a/src/main/java/org/springframework/data/gemfire/function/config/EnableGemfireFunctions.java b/src/main/java/org/springframework/data/gemfire/function/config/EnableGemfireFunctions.java index c82d61ee..dd28d954 100644 --- a/src/main/java/org/springframework/data/gemfire/function/config/EnableGemfireFunctions.java +++ b/src/main/java/org/springframework/data/gemfire/function/config/EnableGemfireFunctions.java @@ -1,11 +1,11 @@ /* * Copyright 2002-2013 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. @@ -22,17 +22,20 @@ import java.lang.annotation.Target; import org.springframework.context.annotation.Import; /** - * - * Enables Gemfire annotated function implementations. Causes the container to discover any beans that are annotated - * with {code} @GemfireFunction {code}, wrap them in a - * {@link org.springframework.data.gemfire.function.PojoFunctionWrapper}, and register them with the cache. - * + * Enables GemFire annotated Function implementations. + * + * Causes the container to discover any beans that are annotated with {code}@GemfireFunction{code}, + * wrap them in a {@link org.springframework.data.gemfire.function.PojoFunctionWrapper}, + * and register them with the cache. + * * @author David Turanski */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented @Inherited -@Import(GemfireFunctionPostBeanProcessorRegistrar.class) +@Documented +@Import(GemfireFunctionBeanPostProcessorRegistrar.class) +@SuppressWarnings("unused") public @interface EnableGemfireFunctions { + } diff --git a/src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionBeanPostProcessor.java b/src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionBeanPostProcessor.java index 2d25b9db..53975f9d 100644 --- a/src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionBeanPostProcessor.java +++ b/src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionBeanPostProcessor.java @@ -1,23 +1,26 @@ /* * Copyright 2002-2013 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.function.config; +import static java.util.Arrays.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; + import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Map; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.gemfire.function.GemfireFunctionUtils; import org.springframework.data.gemfire.function.annotation.GemfireFunction; @@ -25,48 +28,44 @@ import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; /** - * A {@link BeanPostProcessor} to discover components wired as function implementations. That is - * beans that contain methods annotated with {code} @GemfireFunction {code} - * - * @author David Turanski + * Spring {@link BeanPostProcessor} that discovers bean components wired as Function implementations, + * i.e. beans containing methods annotated with {@link GemfireFunction}. * + * @author David Turanski + * @author John Blum + * @see org.springframework.beans.factory.config.BeanPostProcessor + * @see org.springframework.data.gemfire.function.annotation.GemfireFunction */ public class GemfireFunctionBeanPostProcessor implements BeanPostProcessor { - - private static final String GEMFIRE_FUNCTION_ANNOTATION_NAME = GemfireFunction.class.getName(); - - - /* (non-Javadoc) - * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String) - */ - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - return bean; - } /* (non-Javadoc) * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String) */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - - registerAnyDeclaredGemfireFunctionMethods(bean); - + + registerAnyDeclaredGemfireFunctionAnnotatedMethods(bean); + return bean; } - - private void registerAnyDeclaredGemfireFunctionMethods (Object bean) { - - Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass()); - - for (Method method: methods) { - GemfireFunction annotation = AnnotationUtils.getAnnotation(method, GemfireFunction.class); - if (annotation != null) { - Assert.isTrue(Modifier.isPublic(method.getModifiers()),"The method " + method.getName()+ " annotated with" + GEMFIRE_FUNCTION_ANNOTATION_NAME+ " must be public"); - Map attributes = AnnotationUtils.getAnnotationAttributes(annotation,false,true); - GemfireFunctionUtils.registerFunctionForPojoMethod(bean, method, attributes, false); + + private void registerAnyDeclaredGemfireFunctionAnnotatedMethods(Object bean) { + + stream(nullSafeArray(ReflectionUtils.getAllDeclaredMethods(bean.getClass()), Method.class)).forEach(method -> { + + GemfireFunction gemfireFunctionAnnotation = AnnotationUtils.getAnnotation(method, GemfireFunction.class); + + if (gemfireFunctionAnnotation != null) { + + Assert.isTrue(Modifier.isPublic(method.getModifiers()), + String.format("The bean [%s] method [%s] annotated with [%s] must be public", + bean.getClass().getName(), method.getName(), GemfireFunction.class.getName())); + + AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap( + AnnotationUtils.getAnnotationAttributes(gemfireFunctionAnnotation,false,true)); + + GemfireFunctionUtils.registerFunctionForPojoMethod(bean, method, annotationAttributes, false); } - } + }); } - } diff --git a/src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionPostBeanProcessorRegistrar.java b/src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionBeanPostProcessorRegistrar.java similarity index 90% rename from src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionPostBeanProcessorRegistrar.java rename to src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionBeanPostProcessorRegistrar.java index 36548bd0..289936d0 100644 --- a/src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionPostBeanProcessorRegistrar.java +++ b/src/main/java/org/springframework/data/gemfire/function/config/GemfireFunctionBeanPostProcessorRegistrar.java @@ -1,11 +1,11 @@ /* * Copyright 2002-2013 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. @@ -19,12 +19,11 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; /** - * and {@link ImportBeanDefinitionRegistrar} to register the {@link GemfireFunctionBeanPostProcessor} - * - * @author David Turanski + * Spring {@link ImportBeanDefinitionRegistrar} to register the {@link GemfireFunctionBeanPostProcessor} * + * @author David Turanski */ -public class GemfireFunctionPostBeanProcessorRegistrar implements ImportBeanDefinitionRegistrar { +public class GemfireFunctionBeanPostProcessorRegistrar implements ImportBeanDefinitionRegistrar { /* (non-Javadoc) * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry) @@ -34,5 +33,4 @@ public class GemfireFunctionPostBeanProcessorRegistrar implements ImportBeanDefi BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(GemfireFunctionBeanPostProcessor.class); BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); } - } diff --git a/src/main/java/org/springframework/data/gemfire/function/execution/GemfireFunctionOperations.java b/src/main/java/org/springframework/data/gemfire/function/execution/GemfireFunctionOperations.java index bb91a5c2..60c3f150 100644 --- a/src/main/java/org/springframework/data/gemfire/function/execution/GemfireFunctionOperations.java +++ b/src/main/java/org/springframework/data/gemfire/function/execution/GemfireFunctionOperations.java @@ -10,8 +10,8 @@ * 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.function.execution; +package org.springframework.data.gemfire.function.execution; import org.apache.geode.cache.execute.Function; @@ -31,7 +31,7 @@ public interface GemfireFunctionOperations { * @param args an array of Object arguments to the Function call. * @return the contents of the ResultsCollector. */ - public abstract Iterable execute(Function function, Object... args); + Iterable execute(Function function, Object... args); /** * Execute a GemFire Function registered with the given ID. @@ -41,7 +41,7 @@ public interface GemfireFunctionOperations { * @param args an array of Object arguments to the Function call. * @return the results */ - public abstract Iterable execute(String functionId, Object... args); + Iterable execute(String functionId, Object... args); /** * Execute an unregistered GemFire Function with the expected singleton result. @@ -52,7 +52,7 @@ public interface GemfireFunctionOperations { * @return the first item in the ResultsCollector. * @see org.apache.geode.cache.execute.Function */ - public abstract T executeAndExtract(Function function, Object... args); + T executeAndExtract(Function function, Object... args); /** * Execute a GemFire Function registered with an ID and with an expected singleton result @@ -62,7 +62,7 @@ public interface GemfireFunctionOperations { * @param args an array of Object arguments to the Function call. * @return the first item in the results collector */ - public abstract T executeAndExtract(String functionId, Object... args); + T executeAndExtract(String functionId, Object... args); /** * Execute a GemFire Function registered with the given ID having no return value. @@ -70,7 +70,7 @@ public interface GemfireFunctionOperations { * @param functionId the ID under which the GemFire function is registered. * @param args an array of Object arguments to the Function call. */ - public void executeWithNoResult(String functionId, Object... args); + void executeWithNoResult(String functionId, Object... args); /** * Execute a GemFire Function using a native GemFire {@link org.apache.geode.cache.execute.Execution} instance. @@ -80,6 +80,6 @@ public interface GemfireFunctionOperations { * @return the Function execution result. * @see org.springframework.data.gemfire.function.execution.GemfireFunctionCallback */ - public abstract T execute(GemfireFunctionCallback callback); + T execute(GemfireFunctionCallback callback); } diff --git a/src/main/java/org/springframework/data/gemfire/util/RegionUtils.java b/src/main/java/org/springframework/data/gemfire/util/RegionUtils.java new file mode 100644 index 00000000..36f35f56 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/util/RegionUtils.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 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.util; + +import java.util.Optional; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.springframework.util.StringUtils; + +/** + * The RegionUtils class... + * + * @author John Blum + * @since 1.0.0 + */ +public abstract class RegionUtils extends CacheUtils { + + public static boolean isClient(Region region) { + + return Optional.ofNullable(region) + .map(Region::getAttributes) + .map(RegionAttributes::getPoolName) + .filter(StringUtils::hasText) + .isPresent(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/util/RuntimeExceptionFactory.java b/src/main/java/org/springframework/data/gemfire/util/RuntimeExceptionFactory.java index 8b28c5d3..589a5d38 100644 --- a/src/main/java/org/springframework/data/gemfire/util/RuntimeExceptionFactory.java +++ b/src/main/java/org/springframework/data/gemfire/util/RuntimeExceptionFactory.java @@ -58,7 +58,7 @@ public abstract class RuntimeExceptionFactory { * @see java.lang.IllegalArgumentException */ public static IllegalArgumentException newIllegalArgumentException(Throwable cause, - String message, Object... args) { + String message, Object... args) { return new IllegalArgumentException(format(message, args), cause); } diff --git a/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java index d557670d..8d6ede51 100644 --- a/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java @@ -43,6 +43,7 @@ public abstract class SpringUtils { /* (non-Javadoc) */ public static BeanDefinition addDependsOn(BeanDefinition bean, String... beanNames) { + List dependsOnList = new ArrayList<>(); Collections.addAll(dependsOnList, nullSafeArray(bean.getDependsOn(), String.class)); diff --git a/src/test/java/org/springframework/data/gemfire/config/admin/GemfireAdminOperationsUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/admin/GemfireAdminOperationsUnitTests.java new file mode 100644 index 00000000..37174a1e --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/admin/GemfireAdminOperationsUnitTests.java @@ -0,0 +1,290 @@ +/* + * Copyright 2017 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.config.admin; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.query.Index; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefinition; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; + +/** + * The GemfireAdminOperationsUnitTests class... + * + * @author John Blum + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class GemfireAdminOperationsUnitTests { + + @Mock + private GemfireAdminOperations adminOperations; + + private Index mockIndex(String name) { + + Index mockIndex = mock(Index.class, name); + + when(mockIndex.getName()).thenReturn(name); + + return mockIndex; + } + + @SuppressWarnings("unchecked") + private Region mockRegion(String name) { + + Region mockRegion = mock(Region.class, name); + + when(mockRegion.getName()).thenReturn(name); + + return mockRegion; + } + + private SchemaObjectDefinition newGenericSchemaObjectDefinition(String name, SchemaObjectType type) { + return mock(SchemaObjectDefinition.class, name); + } + + @Test + public void createRegionsWithArrayCallsCreateRegion() { + + doCallRealMethod().when(adminOperations).createRegions(ArgumentMatchers.any()); + + RegionDefinition definitionOne = RegionDefinition.from(mockRegion("RegionOne")); + RegionDefinition definitionTwo = RegionDefinition.from(mockRegion("RegionTwo")); + + adminOperations.createRegions(definitionOne, definitionTwo); + + verify(adminOperations, times(1)).createRegion(eq(definitionOne)); + verify(adminOperations, times(1)).createRegion(eq(definitionTwo)); + } + + @Test + public void createRegionsWithEmptyArray() { + + doCallRealMethod().when(adminOperations).createRegions(ArgumentMatchers.any()); + + adminOperations.createRegions(); + + verify(adminOperations, never()).createRegion(any(RegionDefinition.class)); + } + + @Test + public void createRegionsWithNullArray() { + + doCallRealMethod().when(adminOperations).createRegions(ArgumentMatchers.any()); + + adminOperations.createRegions((RegionDefinition[]) null); + + verify(adminOperations, never()).createRegion(any(RegionDefinition.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void createRegionsWithIterableCallsCreateRegion() { + + doCallRealMethod().when(adminOperations).createRegions(any(Iterable.class)); + + RegionDefinition definitionOne = RegionDefinition.from(mockRegion("RegionOne")); + RegionDefinition definitionTwo = RegionDefinition.from(mockRegion("RegionTwo")); + + adminOperations.createRegions(Arrays.asList(definitionOne, definitionTwo)); + + verify(adminOperations, times(1)).createRegion(eq(definitionOne)); + verify(adminOperations, times(1)).createRegion(eq(definitionTwo)); + } + + @Test + @SuppressWarnings("unchecked") + public void createRegionsWithEmptyIterableCallsCreateRegion() { + + doCallRealMethod().when(adminOperations).createRegions(any(Iterable.class)); + + adminOperations.createRegions(Collections.emptyList()); + + verify(adminOperations, never()).createRegion(any(RegionDefinition.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void createRegionsWithNullIterableCallsCreateRegion() { + + adminOperations.createRegions((Iterable) null); + + verify(adminOperations, never()).createRegion(any(RegionDefinition.class)); + } + + @Test + public void createLuceneIndexesWithArrayCallsCreateLuceneIndex() { + + doCallRealMethod().when(adminOperations).createLuceneIndexes(ArgumentMatchers.any()); + + SchemaObjectDefinition definitionOne = newGenericSchemaObjectDefinition("LucenIndexOne", + SchemaObjectType.LUCENE_INDEX); + + SchemaObjectDefinition definitionTwo = newGenericSchemaObjectDefinition("LucenIndexOne", + SchemaObjectType.LUCENE_INDEX); + + adminOperations.createLuceneIndexes(definitionOne, definitionTwo); + + verify(adminOperations, times(1)).createLuceneIndex(eq(definitionOne)); + verify(adminOperations, times(1)).createLuceneIndex(eq(definitionTwo)); + } + + @Test + public void createLuceneIndexesWithEmptyArray() { + + doCallRealMethod().when(adminOperations).createLuceneIndexes(ArgumentMatchers.any()); + + adminOperations.createLuceneIndexes(); + + verify(adminOperations, never()).createLuceneIndex(any(SchemaObjectDefinition.class)); + } + + @Test + public void createLuceneIndexesWithNullArray() { + + doCallRealMethod().when(adminOperations).createLuceneIndexes(ArgumentMatchers.any()); + + adminOperations.createLuceneIndexes((SchemaObjectDefinition) null); + + verify(adminOperations, never()).createLuceneIndex(any(SchemaObjectDefinition.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void createLuceneIndexesWithIterableCallsCreateLuceneIndex() { + + doCallRealMethod().when(adminOperations).createLuceneIndexes(any(Iterable.class)); + + SchemaObjectDefinition definitionOne = newGenericSchemaObjectDefinition("LucenIndexOne", + SchemaObjectType.LUCENE_INDEX); + + SchemaObjectDefinition definitionTwo = newGenericSchemaObjectDefinition("LucenIndexOne", + SchemaObjectType.LUCENE_INDEX); + + adminOperations.createLuceneIndexes(Arrays.asList(definitionOne, definitionTwo)); + + verify(adminOperations, times(1)).createLuceneIndex(eq(definitionOne)); + verify(adminOperations, times(1)).createLuceneIndex(eq(definitionTwo)); + } + + @Test + @SuppressWarnings("unchecked") + public void createLuceneIndexesWithEmptyIterableCallsCreateLuceneIndex() { + + doCallRealMethod().when(adminOperations).createLuceneIndexes(any(Iterable.class)); + + adminOperations.createLuceneIndexes(Collections.emptyList()); + + verify(adminOperations, never()).createLuceneIndex(any(SchemaObjectDefinition.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void createLuceneIndexesWithNullIterableCallsCreateLuceneIndex() { + + adminOperations.createLuceneIndexes((Iterable) null); + + verify(adminOperations, never()).createLuceneIndex(any(SchemaObjectDefinition.class)); + } + + @Test + public void createIndexesWithArrayCallsCreateIndex() { + + doCallRealMethod().when(adminOperations).createIndexes(ArgumentMatchers.any()); + + IndexDefinition definitionOne = IndexDefinition.from(mockIndex("IndexOne")); + IndexDefinition definitionTwo = IndexDefinition.from(mockIndex("IndexTwo")); + + adminOperations.createIndexes(definitionOne, definitionTwo); + + verify(adminOperations, times(1)).createIndex(eq(definitionOne)); + verify(adminOperations, times(1)).createIndex(eq(definitionTwo)); + } + + @Test + public void createIndexesWithEmptyArray() { + + doCallRealMethod().when(adminOperations).createIndexes(ArgumentMatchers.any()); + + adminOperations.createIndexes(); + + verify(adminOperations, never()).createIndex(any(IndexDefinition.class)); + } + + @Test + public void createIndexesWithNullArray() { + + doCallRealMethod().when(adminOperations).createIndexes(ArgumentMatchers.any()); + + adminOperations.createIndexes(); + + verify(adminOperations, never()).createIndex(any(IndexDefinition.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void createIndexesWithIterableCallsCreateIndex() { + + doCallRealMethod().when(adminOperations).createIndexes(any(Iterable.class)); + + IndexDefinition definitionOne = IndexDefinition.from(mockIndex("IndexOne")); + IndexDefinition definitionTwo = IndexDefinition.from(mockIndex("IndexTwo")); + + adminOperations.createIndexes(Arrays.asList(definitionOne, definitionTwo)); + + verify(adminOperations, times(1)).createIndex(eq(definitionOne)); + verify(adminOperations, times(1)).createIndex(eq(definitionTwo)); + } + + @Test + @SuppressWarnings("unchecked") + public void createIndexesWithEmptyIterable() { + + doCallRealMethod().when(adminOperations).createIndexes(any(Iterable.class)); + + adminOperations.createIndexes(Collections.emptyList()); + + verify(adminOperations, never()).createIndex(any(IndexDefinition.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void createIndexesWithNullIterable() { + + adminOperations.createIndexes((Iterable) null); + + verify(adminOperations, never()).createIndex(any(IndexDefinition.class)); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/admin/functions/CreateIndexFunctionUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/admin/functions/CreateIndexFunctionUnitTests.java new file mode 100644 index 00000000..15b82b78 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/admin/functions/CreateIndexFunctionUnitTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2017 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.config.admin.functions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.query.Index; +import org.apache.geode.cache.query.QueryException; +import org.apache.geode.cache.query.QueryService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; + +/** + * Unit tests for {@link CreateIndexFunction}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.query.Index + * @see org.apache.geode.cache.query.QueryService + * @see org.springframework.data.gemfire.config.schema.definitions.IndexDefinition + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class CreateIndexFunctionUnitTests { + + @Mock + private Cache mockCache; + + private CreateIndexFunction createIndexFunction; + + @Mock + private Index mockIndex; + + @Mock + private QueryService mockQueryService; + + @Before + public void setup() { + + this.createIndexFunction = spy(new CreateIndexFunction()); + + doReturn(this.mockCache).when(this.createIndexFunction).resolveCache(); + when(this.mockCache.getQueryService()).thenReturn(this.mockQueryService); + when(this.mockIndex.getName()).thenReturn("MockIndex"); + } + + @Test + public void createsFunctionalIndex() throws QueryException { + + when(this.mockIndex.getIndexedExpression()).thenReturn("age"); + when(this.mockIndex.getFromClause()).thenReturn("/Customers"); + when(this.mockIndex.getType()).thenReturn(IndexType.FUNCTIONAL.getGemfireIndexType()); + when(this.mockQueryService.getIndexes()).thenReturn(null); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(this.createIndexFunction.createIndex(indexDefinition)).isTrue(); + + verify(this.mockCache, times(2)).getQueryService(); + verify(this.mockQueryService, times(1)).getIndexes(); + verify(this.mockQueryService, times(1)) + .createIndex(eq("MockIndex"), eq("age"), eq("/Customers")); + } + + @Test + public void createsHashIndex() throws QueryException { + + Index mockIndexTwo = mock(Index.class, "MockIndexTwo"); + + when(mockIndexTwo.getName()).thenReturn("MockIndexTwo"); + when(this.mockIndex.getIndexedExpression()).thenReturn("name"); + when(this.mockIndex.getFromClause()).thenReturn("/Customers"); + when(this.mockIndex.getType()).thenReturn(IndexType.HASH.getGemfireIndexType()); + when(this.mockQueryService.getIndexes()).thenReturn(Collections.singleton(mockIndexTwo)); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(this.createIndexFunction.createIndex(indexDefinition)).isTrue(); + + verify(this.mockCache, times(2)).getQueryService(); + verify(this.mockQueryService, times(1)).getIndexes(); + verify(mockIndexTwo, times(1)).getName(); + verify(this.mockQueryService, times(1)) + .createHashIndex(eq("MockIndex"), eq("name"), eq("/Customers")); + } + + @Test + public void createsKeyIndex() throws QueryException { + + when(this.mockIndex.getIndexedExpression()).thenReturn("id"); + when(this.mockIndex.getFromClause()).thenReturn("/Customers"); + when(this.mockIndex.getType()).thenReturn(IndexType.PRIMARY_KEY.getGemfireIndexType()); + when(this.mockQueryService.getIndexes()).thenReturn(Collections.emptyList()); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(this.createIndexFunction.createIndex(indexDefinition)).isTrue(); + + verify(this.mockCache, times(2)).getQueryService(); + verify(this.mockQueryService, times(1)).getIndexes(); + verify(this.mockQueryService, times(1)) + .createKeyIndex(eq("MockIndex"), eq("id"), eq("/Customers")); + } + + @Test + public void doesNotCreateIndexWhenIndexAlreadyExists() throws QueryException { + + when(this.mockQueryService.getIndexes()).thenReturn(Collections.singleton(this.mockIndex)); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(this.createIndexFunction.createIndex(indexDefinition)).isFalse(); + + verify(this.mockCache, times(1)).getQueryService(); + verify(this.mockQueryService, times(1)).getIndexes(); + verify(this.mockQueryService, never()).createIndex(anyString(), anyString(), anyString()); + verify(this.mockQueryService, never()).createIndex(anyString(), anyString(), anyString(), anyString()); + verify(this.mockQueryService, never()).createHashIndex(anyString(), anyString(), anyString()); + verify(this.mockQueryService, never()).createHashIndex(anyString(), anyString(), anyString(), anyString()); + verify(this.mockQueryService, never()).createKeyIndex(anyString(), anyString(), anyString()); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/admin/functions/CreateRegionFunctionUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/admin/functions/CreateRegionFunctionUnitTests.java new file mode 100644 index 00000000..04e0464e --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/admin/functions/CreateRegionFunctionUnitTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2017 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.config.admin.functions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionFactory; +import org.apache.geode.cache.RegionShortcut; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; + +/** + * Unit tests for {@link CreateRegionFunction}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.Region + * @see org.springframework.data.gemfire.config.schema.definitions.RegionDefinition + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class CreateRegionFunctionUnitTests { + + @Mock + private Cache mockCache; + + @Mock + private Region mockRegion; + + private CreateRegionFunction createRegionFunction; + + @Before + public void setup() { + + this.createRegionFunction = spy(new CreateRegionFunction()); + + doReturn(this.mockCache).when(this.createRegionFunction).resolveCache(); + when(this.mockRegion.getName()).thenReturn("MockRegion"); + } + + @Test + @SuppressWarnings("unchecked") + public void createsRegionWhenRegionDoesNotExist() { + + RegionDefinition regionDefinition = RegionDefinition.from(this.mockRegion); + + RegionFactory mockRegionFactory = mock(RegionFactory.class); + + when(this.mockCache.getRegion(anyString())).thenReturn(null); + when(this.mockCache.createRegionFactory(any(RegionShortcut.class))).thenReturn(mockRegionFactory); + + assertThat(this.createRegionFunction.createRegion(regionDefinition)).isTrue(); + + verify(this.mockCache, times(1)).getRegion(eq("MockRegion")); + verify(this.mockCache, times(1)).createRegionFactory(eq(RegionShortcut.PARTITION)); + verify(mockRegionFactory, times(1)).create(eq("MockRegion")); + } + + @Test + public void doesNotCreateRegionWhenRegionAlreadyExists() { + + RegionDefinition regionDefinition = RegionDefinition.from(this.mockRegion); + + when(this.mockCache.getRegion(anyString())).thenReturn(this.mockRegion); + + assertThat(this.createRegionFunction.createRegion(regionDefinition)).isFalse(); + + verify(this.mockCache, times(1)).getRegion(eq("MockRegion")); + verify(this.mockCache, never()).createRegionFactory(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/admin/functions/ListIndexesFunctionUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/admin/functions/ListIndexesFunctionUnitTests.java new file mode 100644 index 00000000..bac54468 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/admin/functions/ListIndexesFunctionUnitTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2017 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.config.admin.functions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.query.Index; +import org.apache.geode.cache.query.QueryService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Unit tests for {@link ListIndexesFunction}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.query.Index + * @see org.apache.geode.cache.query.QueryService + * @see org.springframework.data.gemfire.config.admin.functions.ListIndexesFunction + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class ListIndexesFunctionUnitTests { + + @Mock + private Cache mockCache; + + @Mock + private Index mockIndexOne; + + @Mock + private Index mockIndexTwo; + + private ListIndexesFunction listIndexesFunction; + + @Mock + private QueryService mockQueryService; + + @Before + public void setup() { + + this.listIndexesFunction = spy(new ListIndexesFunction()); + + doReturn(this.mockCache).when(this.listIndexesFunction).resolveCache(); + when(this.mockCache.getQueryService()).thenReturn(this.mockQueryService); + when(this.mockIndexOne.getName()).thenReturn("MockIndexOne"); + when(this.mockIndexTwo.getName()).thenReturn("MockIndexTwo"); + } + + @Test + public void listIndexesReturnsIndexNames() { + + when(this.mockQueryService.getIndexes()).thenReturn(Arrays.asList(this.mockIndexOne, this.mockIndexTwo)); + + assertThat(this.listIndexesFunction.listIndexes()).contains("MockIndexOne", "MockIndexTwo"); + + verify(this.listIndexesFunction, times(1)).resolveCache(); + verify(this.mockCache, times(1)).getQueryService(); + verify(this.mockQueryService, times(1)).getIndexes(); + verify(this.mockIndexOne, times(1)).getName(); + verify(this.mockIndexTwo, times(1)).getName(); + } + + @Test + public void listIndexesReturnsEmptySetWhenCacheIsNull() { + + doReturn(null).when(this.listIndexesFunction).resolveCache(); + + assertThat(this.listIndexesFunction.listIndexes()).isEmpty(); + + verify(this.listIndexesFunction, times(1)).resolveCache(); + } + + @Test + public void listIndexesReturnsEmptySetWhenQueryServiceIsNull() { + + when(this.mockCache.getQueryService()).thenReturn(null); + + assertThat(this.listIndexesFunction.listIndexes()).isEmpty(); + + verify(this.listIndexesFunction, times(1)).resolveCache(); + verify(this.mockCache, times(1)).getQueryService(); + } + + @Test + public void listIndexesReturnsEmptySetWhenQueryServiceGetIndexesIsNull() { + + when(this.mockQueryService.getIndexes()).thenReturn(null); + + assertThat(this.listIndexesFunction.listIndexes()).isEmpty(); + + verify(this.listIndexesFunction, times(1)).resolveCache(); + verify(this.mockCache, times(1)).getQueryService(); + verify(this.mockQueryService, times(1)).getIndexes(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/admin/remote/FunctionGemfireAdminTemplateUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/admin/remote/FunctionGemfireAdminTemplateUnitTests.java new file mode 100644 index 00000000..33da4092 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/admin/remote/FunctionGemfireAdminTemplateUnitTests.java @@ -0,0 +1,233 @@ +/* + * Copyright 2017 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.config.admin.remote; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.util.ArrayUtils.asArray; +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; + +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.Scope; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.execute.Function; +import org.apache.geode.cache.query.Index; +import org.apache.geode.management.internal.cli.domain.RegionInformation; +import org.apache.geode.management.internal.cli.functions.GetRegionsFunction; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.client.function.ListRegionsOnServerFunction; +import org.springframework.data.gemfire.config.admin.functions.CreateIndexFunction; +import org.springframework.data.gemfire.config.admin.functions.CreateRegionFunction; +import org.springframework.data.gemfire.config.admin.functions.ListIndexesFunction; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; +import org.springframework.data.gemfire.function.execution.GemfireFunctionOperations; + +/** + * Unit tests for {@link FunctionGemfireAdminTemplate}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.admin.remote.FunctionGemfireAdminTemplate + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class FunctionGemfireAdminTemplateUnitTests { + + private FunctionGemfireAdminTemplate template; + + @Mock + private ClientCache mockClientCache; + + @Mock + private GemfireFunctionOperations mockFunctionOperations; + + @Mock + private Index mockIndex; + + @Mock + private Region mockRegion; + + @Before + public void setup() { + + this.template = spy(new FunctionGemfireAdminTemplate(this.mockClientCache)); + + when(this.template.newGemfireFunctionOperations(any(ClientCache.class))) + .thenReturn(this.mockFunctionOperations); + + when(this.mockIndex.getName()).thenReturn("MockIndex"); + when(this.mockRegion.getName()).thenReturn("MockRegion"); + } + + private Region mockRegion(String name) { + + Region mockRegion = mock(Region.class, name); + + when(mockRegion.getFullPath()).thenReturn(String.format("%1$s%2$s", Region.SEPARATOR, name)); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class, + String.format("Mock%sRegionAttributes", name)); + + when(mockRegionAttributes.getDataPolicy()).thenReturn(DataPolicy.PARTITION); + when(mockRegionAttributes.getScope()).thenReturn(Scope.DISTRIBUTED_ACK); + when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes); + + return mockRegion; + } + + private RegionInformation newRegionInformation(String regionName) { + return new RegionInformation(mockRegion(regionName), false); + } + + @Test + public void constructFunctionGemfireAdminTemplateWithClientCache() { + + FunctionGemfireAdminTemplate template = new FunctionGemfireAdminTemplate(this.mockClientCache); + + assertThat(template).isNotNull(); + assertThat(template.getClientCache()).isSameAs(this.mockClientCache); + } + + @Test(expected = IllegalArgumentException.class) + public void constructFunctionGemfireAdminTemplateWithNull() { + + try { + new FunctionGemfireAdminTemplate(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("ClientCache is required"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void getAvailableServerRegionsExecutesListRegionsOnServerFunction() { + + when(this.mockFunctionOperations.executeAndExtract(any(Function.class))) + .thenReturn(asSet("RegionOne", "RegionTwo")); + + Iterable availableServerRegions = this.template.getAvailableServerRegions(); + + assertThat(availableServerRegions).isNotNull(); + assertThat(availableServerRegions).hasSize(2); + assertThat(availableServerRegions).contains("RegionOne", "RegionTwo"); + + verify(this.mockFunctionOperations, times(1)) + .executeAndExtract(isA(ListRegionsOnServerFunction.class)); + } + + @Test + public void getAvailableServerRegionsExecutesGetRegionsFunction() { + + when(this.mockFunctionOperations.executeAndExtract(isA(ListRegionsOnServerFunction.class))) + .thenThrow(new RuntimeException("TEST")); + + Object[] regionInformation = asArray(newRegionInformation("MockRegionOne"), + newRegionInformation("MockRegionTwo")); + + when(this.mockFunctionOperations.executeAndExtract(isA(GetRegionsFunction.class))) + .thenReturn(regionInformation); + + Iterable availableServerRegions = this.template.getAvailableServerRegions(); + + assertThat(availableServerRegions).isNotNull(); + assertThat(availableServerRegions).hasSize(2); + assertThat(availableServerRegions).contains("MockRegionOne", "MockRegionTwo"); + + verify(this.mockFunctionOperations, times(1)) + .executeAndExtract(isA(ListRegionsOnServerFunction.class)); + + verify(this.mockFunctionOperations, times(1)) + .executeAndExtract(isA(GetRegionsFunction.class)); + } + + @Test + public void getAvailableServerRegionIndexesCallsExecuteWithListIndexesFunctionId() { + + this.template.getAvailableServerRegionIndexes(); + + verify(this.mockFunctionOperations, times(1)) + .executeAndExtract(eq(ListIndexesFunction.LIST_INDEXES_FUNCTION_ID)); + } + + @Test + public void createRegionCallsExecuteWithCreateRegionFunctionIdAndRegionDefinition() { + + RegionDefinition regionDefinition = RegionDefinition.from(this.mockRegion); + + this.template.createRegion(regionDefinition); + + verify(this.mockFunctionOperations, times(1)) + .executeAndExtract(eq(CreateRegionFunction.CREATE_REGION_FUNCTION_ID), eq(regionDefinition)); + } + + @Test + public void createIndexCallsExecuteWithCreateIndexFunctionIdAndIndexDefinition() { + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + this.template.createIndex(indexDefinition); + + verify(this.mockFunctionOperations, times(1)) + .executeAndExtract(eq(CreateIndexFunction.CREATE_INDEX_FUNCTION_ID), eq(indexDefinition)); + } + + @Test + public void containsRegionInformationIsNullSafe() { + assertThat(this.template.containsRegionInformation(null)).isFalse(); + } + + @Test + public void containsRegionInformationReturnsFalseForNonObjectArrayResult() { + assertThat(this.template.containsRegionInformation(newRegionInformation("TestRegion"))).isFalse(); + } + + @Test + public void containsRegionInformationReturnsFalseForEmptyObjectArrayResult() { + assertThat(this.template.containsRegionInformation(asArray())).isFalse(); + } + + @Test + public void containsRegionInformationReturnsFalseForObjectArrayContainingNonRegionInformation() { + assertThat(this.template.containsRegionInformation(asArray(mockRegion("TestRegion")))).isFalse(); + } + + @Test + public void containsRegionInformationReturnsTrueForObjectArrayWithRegionInformation() { + assertThat(this.template.containsRegionInformation(asArray(newRegionInformation("TestRegion")))) + .isTrue(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateUnitTests.java new file mode 100644 index 00000000..71707e35 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateUnitTests.java @@ -0,0 +1,195 @@ +/* + * Copyright 2017 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.config.admin.remote; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.query.Index; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestOperations; + +/** + * Unit tests for {@link RestHttpGemfireAdminTemplate}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class RestHttpGemfireAdminTemplateUnitTests { + + @Mock + private ClientCache mockClientCache; + + @Mock + private Index mockIndex; + + @Mock + private Region mockRegion; + + private RestHttpGemfireAdminTemplate template; + + @Mock + private RestOperations mockRestOperations; + + @Before + public void setup() { + + this.template = new RestHttpGemfireAdminTemplate(this.mockClientCache) { + @Override protected RestOperations newRestOperations() { + return mockRestOperations; + } + }; + + when(this.mockRegion.getName()).thenReturn("MockRegion"); + when(this.mockIndex.getName()).thenReturn("MockIndex"); + when(this.mockIndex.getIndexedExpression()).thenReturn("age"); + when(this.mockIndex.getFromClause()).thenReturn("/Customers"); + when(this.mockIndex.getType()).thenReturn(IndexType.FUNCTIONAL.getGemfireIndexType()); + } + + @Test + public void constructDefaultRestHttpGemfireAdminTemplate() { + + RestHttpGemfireAdminTemplate template = new RestHttpGemfireAdminTemplate(this.mockClientCache); + + assertThat(template).isNotNull(); + assertThat(template.getClientCache()).isSameAs(this.mockClientCache); + assertThat(template.getManagementRestApiUrl()) + .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, + RestHttpGemfireAdminTemplate.DEFAULT_HOST, RestHttpGemfireAdminTemplate.DEFAULT_PORT)); + assertThat(template.getRestOperations()).isNotNull(); + } + + @Test + public void constructCustomRestHttpGemfireAdminTemplate() { + + RestHttpGemfireAdminTemplate template = + new RestHttpGemfireAdminTemplate(this.mockClientCache, "skullbox", 8080); + + assertThat(template).isNotNull(); + assertThat(template.getClientCache()).isSameAs(this.mockClientCache); + assertThat(template.getManagementRestApiUrl()) + .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, "skullbox", 8080)); + assertThat(template.getRestOperations()).isNotNull(); + } + + @Test + @SuppressWarnings("unchecked") + public void createIndexCallsGemFireManagementRestApi() { + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + when(this.mockRestOperations.exchange(any(RequestEntity.class), eq(String.class))).thenAnswer(invocation -> { + + RequestEntity requestEntity = invocation.getArgument(0); + + assertThat(requestEntity).isNotNull(); + assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST); + assertThat(requestEntity.getUrl()).isEqualTo(URI.create("http://localhost:7070/gemfire/v1/indexes")); + + HttpHeaders headers = requestEntity.getHeaders(); + + assertThat(headers).isNotNull(); + assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED); + + Object body = requestEntity.getBody(); + + assertThat(body).isInstanceOf(MultiValueMap.class); + + MultiValueMap requestBody = (MultiValueMap) body; + + assertThat(requestBody.getFirst("name")).isEqualTo(indexDefinition.getName()); + assertThat(requestBody.getFirst("expression")).isEqualTo(indexDefinition.getExpression()); + assertThat(requestBody.getFirst("region")).isEqualTo(indexDefinition.getFromClause()); + assertThat(requestBody.getFirst("type")).isEqualTo(indexDefinition.getIndexType().toString()); + + return new ResponseEntity(HttpStatus.OK); + }); + + this.template.createIndex(indexDefinition); + + verify(this.mockRestOperations, times(1)) + .exchange(isA(RequestEntity.class), eq(String.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void createRegionCallsGemFireManagementRestApi() { + + RegionDefinition regionDefinition = RegionDefinition.from(this.mockRegion); + + when(this.mockRestOperations.exchange(any(RequestEntity.class), eq(String.class))).thenAnswer(invocation -> { + + RequestEntity requestEntity = invocation.getArgument(0); + + assertThat(requestEntity).isNotNull(); + assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST); + assertThat(requestEntity.getUrl()).isEqualTo(URI.create("http://localhost:7070/gemfire/v1/regions")); + + HttpHeaders headers = requestEntity.getHeaders(); + + assertThat(headers).isNotNull(); + assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED); + + Object body = requestEntity.getBody(); + + assertThat(body).isInstanceOf(MultiValueMap.class); + + MultiValueMap requestBody = (MultiValueMap) body; + + assertThat(requestBody.getFirst("name")).isEqualTo(regionDefinition.getName()); + assertThat(requestBody.getFirst("type")).isEqualTo(regionDefinition.getRegionShortcut().toString()); + assertThat(requestBody.getFirst("skip-if-exists")) + .isEqualTo(String.valueOf(RestHttpGemfireAdminTemplate.CREATE_REGION_SKIP_IF_EXISTS_DEFAULT)); + + return new ResponseEntity(HttpStatus.OK); + }); + + this.template.createRegion(regionDefinition); + + verify(this.mockRestOperations, times(1)) + .exchange(isA(RequestEntity.class), eq(String.class)); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationIntegrationTests.java new file mode 100644 index 00000000..fdf07156 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationIntegrationTests.java @@ -0,0 +1,267 @@ +/* + * Copyright 2017 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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collections; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Import; +import org.springframework.data.gemfire.IndexFactoryBean; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.PartitionedRegionFactoryBean; +import org.springframework.data.gemfire.ReplicatedRegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.config.admin.functions.CreateIndexFunction; +import org.springframework.data.gemfire.config.admin.functions.CreateRegionFunction; +import org.springframework.data.gemfire.config.admin.functions.ListIndexesFunction; +import org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate; +import org.springframework.data.gemfire.function.config.EnableGemfireFunctions; +import org.springframework.data.gemfire.process.ProcessWrapper; +import org.springframework.data.gemfire.support.ConnectionEndpoint; +import org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for the {@link EnableClusterConfiguration} annotation + * and {@link ClusterConfigurationConfiguration} class. + * + * @author John Blum + * @see org.junit.Test + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.Import + * @see org.springframework.data.gemfire.IndexFactoryBean + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + * @see org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration + * @see org.springframework.data.gemfire.config.annotation.EnableClusterConfiguration + * @see org.springframework.data.gemfire.process.ProcessWrapper + * @see org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 2.0.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = EnableClusterConfigurationIntegrationTests.TestConfiguration.class) +@SuppressWarnings("unused") +public class EnableClusterConfigurationIntegrationTests extends ClientServerIntegrationTestsSupport { + + private static final String LOG_LEVEL = "warning"; + + private static ProcessWrapper gemfireServer; + + @Autowired + private ClientCache gemfireCache; + + private GemfireAdminOperations adminOperations; + + @BeforeClass + public static void startGemFireServer() throws Exception { + + int availablePort = findAvailablePort(); + + gemfireServer = run(GemFireServerConfiguration.class, + String.format("-D%s=%d", GEMFIRE_CACHE_SERVER_PORT_PROPERTY, availablePort)); + + waitForServerToStart("localhost", availablePort); + + System.setProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, String.valueOf(availablePort)); + } + + @AfterClass + public static void stopGemFireServer() { + System.clearProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY); + stop(gemfireServer); + } + + @Before + public void setup() { + this.adminOperations = new RestHttpGemfireAdminTemplate(this.gemfireCache, + "localhost", Integer.getInteger(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, 40404)); + } + + @Test + public void serverIndexesAreCorrect() { + assertThat(this.adminOperations.getAvailableServerRegionIndexes()) + .containsAll(Arrays.asList("IndexOne", "IndexTwo")); + } + + @Test + public void serverRegionsAreCorrect() { + assertThat(this.adminOperations.getAvailableServerRegions()) + .containsAll(Arrays.asList("RegionOne", "RegionTwo", "RegionThree", "RegionFour")); + } + + @Configuration + @EnableClusterConfiguration + @Import(GemFireClientConfiguration.class) + static class TestConfiguration { + } + + @ClientCacheApplication(logLevel = LOG_LEVEL, subscriptionEnabled = true) + static class GemFireClientConfiguration { + + @Bean + ClientCacheConfigurer clientCachePoolPortConfigurer( + @Value("${" + GEMFIRE_CACHE_SERVER_PORT_PROPERTY + ":40404}") int port) { + + return (bean, clientCacheFactoryBean) -> clientCacheFactoryBean.setServers( + Collections.singletonList(new ConnectionEndpoint("localhost", port))); + } + + @Bean("IndexOne") + @DependsOn("RegionOne") + IndexFactoryBean indexOne(GemFireCache gemfireCache) { + + IndexFactoryBean indexFactory = new IndexFactoryBean(); + + indexFactory.setCache(gemfireCache); + indexFactory.setExpression("id"); + indexFactory.setFrom("/RegionOne"); + indexFactory.setType(IndexType.KEY); + + return indexFactory; + } + + @Bean("RegionOne") + ClientRegionFactoryBean regionOne(GemFireCache gemfireCache) { + + ClientRegionFactoryBean clientRegionFactory = new ClientRegionFactoryBean<>(); + + clientRegionFactory.setCache(gemfireCache); + clientRegionFactory.setClose(false); + clientRegionFactory.setShortcut(ClientRegionShortcut.PROXY); + + return clientRegionFactory; + } + + @Bean("RegionTwo") + ClientRegionFactoryBean regionTwo(GemFireCache gemfireCache) { + + ClientRegionFactoryBean clientRegionFactory = new ClientRegionFactoryBean<>(); + + clientRegionFactory.setCache(gemfireCache); + clientRegionFactory.setClose(false); + clientRegionFactory.setShortcut(ClientRegionShortcut.PROXY); + + return clientRegionFactory; + } + + @Bean("RegionThree") + ClientRegionFactoryBean regionThree(GemFireCache gemfireCache) { + + ClientRegionFactoryBean clientRegionFactory = new ClientRegionFactoryBean<>(); + + clientRegionFactory.setCache(gemfireCache); + clientRegionFactory.setClose(false); + clientRegionFactory.setShortcut(ClientRegionShortcut.CACHING_PROXY); + + return clientRegionFactory; + } + } + + @CacheServerApplication(name = "EnableClusterConfigurationIntegrationTests", logLevel = LOG_LEVEL) + @EnableGemfireFunctions + static class GemFireServerConfiguration { + + public static void main(String[] args) { + + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext( + EnableClusterConfigurationIntegrationTests.GemFireServerConfiguration.class); + + applicationContext.registerShutdownHook(); + } + + @Bean + CreateIndexFunction createIndexFunction() { + return new CreateIndexFunction(); + } + + @Bean + CreateRegionFunction createRegionFunction() { + return new CreateRegionFunction(); + } + + @Bean + ListIndexesFunction listIndexesFunction() { + return new ListIndexesFunction(); + } + + @Bean + CacheServerConfigurer cacheServerPortConfigurer( + @Value("${" + GEMFIRE_CACHE_SERVER_PORT_PROPERTY + ":40404}") int port) { + + return (bean, cacheServerFactoryBean) -> cacheServerFactoryBean.setPort(port); + } + + @Bean("IndexTwo") + @DependsOn("RegionTwo") + IndexFactoryBean indexTwo(GemFireCache gemfireCache) { + + IndexFactoryBean indexFactory = new IndexFactoryBean(); + + indexFactory.setCache(gemfireCache); + indexFactory.setExpression("name"); + indexFactory.setFrom("/RegionTwo"); + indexFactory.setType(IndexType.HASH); + + return indexFactory; + } + + @Bean("RegionTwo") + PartitionedRegionFactoryBean regionTwo(GemFireCache gemfireCache) { + + PartitionedRegionFactoryBean regionFactoryBean = new PartitionedRegionFactoryBean<>(); + + regionFactoryBean.setCache(gemfireCache); + regionFactoryBean.setClose(false); + regionFactoryBean.setPersistent(false); + + return regionFactoryBean; + } + + @Bean("RegionFour") + ReplicatedRegionFactoryBean regionFour(GemFireCache gemfireCache) { + + ReplicatedRegionFactoryBean regionFactoryBean = new ReplicatedRegionFactoryBean<>(); + + regionFactoryBean.setCache(gemfireCache); + regionFactoryBean.setClose(false); + regionFactoryBean.setPersistent(false); + + return regionFactoryBean; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/SchemaObjectDefinitionUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/SchemaObjectDefinitionUnitTests.java new file mode 100644 index 00000000..0f186f5c --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/SchemaObjectDefinitionUnitTests.java @@ -0,0 +1,153 @@ +/* + * Copyright 2017 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.config.schema; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.springframework.core.Ordered; + +/** + * Unit tests for {@link SchemaObjectDefinition}. + * + * @author John Blum + * @see org.junit.Test + * @see org.springframework.data.gemfire.config.schema.SchemaObjectDefinition + * @since 2.0.0 + */ +public class SchemaObjectDefinitionUnitTests { + + private SchemaObjectDefinition newSchemaObjectDefinition(String name) { + return new TestSchemaObjectDefinition(name); + } + + @Test + public void constructSchemaObjectDefinitionWithName() { + + SchemaObjectDefinition schemaObjectDefinition = newSchemaObjectDefinition("TEST"); + + assertThat(schemaObjectDefinition).isNotNull(); + assertThat(schemaObjectDefinition.getName()).isEqualTo("TEST"); + assertThat(schemaObjectDefinition.getType()).isEqualTo(SchemaObjectType.UNKNOWN); + } + + private SchemaObjectDefinition testConstructSchemaObjectDefinitionWithInvalidName(String name) { + try { + return newSchemaObjectDefinition(name); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Name [%s] is required", name); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test(expected = IllegalArgumentException.class) + public void constructSchemaObjectDefinitionWithNull() { + testConstructSchemaObjectDefinitionWithInvalidName(null); + } + + @Test(expected = IllegalArgumentException.class) + public void constructSchemaObjectDefinitionWithNoName() { + testConstructSchemaObjectDefinitionWithInvalidName(" "); + } + + @Test(expected = IllegalArgumentException.class) + public void constructSchemaObjectDefinitionWithEmptyName() { + testConstructSchemaObjectDefinitionWithInvalidName(""); + } + + @Test + public void equalSchemaObjectDefinitionsAreEqual() { + + SchemaObjectDefinition schemaObjectDefinitionOne = newSchemaObjectDefinition("TEST"); + SchemaObjectDefinition schemaObjectDefinitionTwo = newSchemaObjectDefinition("TEST"); + + assertThat(schemaObjectDefinitionOne).isNotSameAs(schemaObjectDefinitionTwo); + assertThat(schemaObjectDefinitionOne).isEqualTo(schemaObjectDefinitionTwo); + } + + @Test + public void identicalSchemaObjectDefinitionsAreEquals() { + + SchemaObjectDefinition schemaObjectDefinition = newSchemaObjectDefinition("TEST"); + + assertThat(schemaObjectDefinition).isEqualTo(schemaObjectDefinition); + } + + @Test + public void unequalSchemaObjectDefinitionsAreNotEqual() { + + SchemaObjectDefinition schemaObjectDefinitionOne = newSchemaObjectDefinition("TEST"); + SchemaObjectDefinition schemaObjectDefinitionTwo = newSchemaObjectDefinition("test"); + + assertThat(schemaObjectDefinitionOne).isNotSameAs(schemaObjectDefinitionTwo); + assertThat(schemaObjectDefinitionOne).isNotEqualTo(schemaObjectDefinitionTwo); + } + + @Test + public void equalSchemaObjectDefinitionsHaveTheSameHashCode() { + + SchemaObjectDefinition schemaObjectDefinitionOne = newSchemaObjectDefinition("TEST"); + SchemaObjectDefinition schemaObjectDefinitionTwo = newSchemaObjectDefinition("TEST"); + + assertThat(schemaObjectDefinitionOne).isNotSameAs(schemaObjectDefinitionTwo); + assertThat(schemaObjectDefinitionOne.hashCode()).isEqualTo(schemaObjectDefinitionTwo.hashCode()); + } + + @Test + public void identicalSchemaObjectDefinitionsHaveTheSameHashCode() { + + SchemaObjectDefinition schemaObjectDefinition = newSchemaObjectDefinition("TEST"); + + assertThat(schemaObjectDefinition.hashCode()).isEqualTo(schemaObjectDefinition.hashCode()); + } + + @Test + public void unequalSchemaObjectDefinitionsHaveDifferentHashCodes() { + + SchemaObjectDefinition schemaObjectDefinitionOne = newSchemaObjectDefinition("TEST"); + SchemaObjectDefinition schemaObjectDefinitionTwo = newSchemaObjectDefinition("test"); + + assertThat(schemaObjectDefinitionOne).isNotSameAs(schemaObjectDefinitionTwo); + assertThat(schemaObjectDefinitionOne.hashCode()).isNotEqualTo(schemaObjectDefinitionTwo.hashCode()); + } + + @Test + public void toStringPrintsNameAndType() { + assertThat(newSchemaObjectDefinition("test").toString()).isEqualTo("UNKNOWN[test]"); + } + + private static final class TestSchemaObjectDefinition extends SchemaObjectDefinition { + + private TestSchemaObjectDefinition(String name) { + super(name); + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public SchemaObjectType getType() { + return SchemaObjectType.UNKNOWN; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/SchemaObjectTypeUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/SchemaObjectTypeUnitTests.java new file mode 100644 index 00000000..7d07719c --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/SchemaObjectTypeUnitTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017 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.config.schema; + +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.DiskStore; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.asyncqueue.AsyncEventQueue; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.Pool; +import org.apache.geode.cache.execute.Function; +import org.apache.geode.cache.lucene.LuceneIndex; +import org.apache.geode.cache.query.Index; +import org.apache.geode.cache.wan.GatewayReceiver; +import org.apache.geode.cache.wan.GatewaySender; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Unit tests for {@link SchemaObjectType}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.schema.SchemaObjectType + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class SchemaObjectTypeUnitTests { + + @Test + public void objectTypesAreSetAndCorrect() { + + Set> expectedSchemaObjectTypes = asSet(AsyncEventQueue.class, Cache.class, ClientCache.class, + DiskStore.class, Function.class, GatewayReceiver.class, GatewaySender.class, Index.class, LuceneIndex.class, + Pool.class, Region.class, Void.class); + + Set> actualSchemaObjectTypes = stream(SchemaObjectType.values()) + .map(SchemaObjectType::getObjectType) + .collect(Collectors.toSet()); + + assertThat(actualSchemaObjectTypes).hasSameSizeAs(expectedSchemaObjectTypes); + assertThat(actualSchemaObjectTypes).containsAll(expectedSchemaObjectTypes); + } + + @Test + public void fromClass() { + stream(SchemaObjectType.values()).forEach(it -> + assertThat(SchemaObjectType.from(it.getObjectType())).isSameAs(it)); + } + + @Test + public void fromNullIsUnknown() { + assertThat(SchemaObjectType.from(null)).isSameAs(SchemaObjectType.UNKNOWN); + assertThat(SchemaObjectType.from((Object) null)).isSameAs(SchemaObjectType.UNKNOWN); + } + + @Test + public void fromObject() { + stream(SchemaObjectType.values()).filter(it -> !SchemaObjectType.UNKNOWN.equals(it)).forEach(it -> + assertThat(SchemaObjectType.from(mock(it.getObjectType()))).isSameAs(it)); + } + + @Test + public void fromUntypedObjectIsUnknown() { + assertThat(SchemaObjectType.from(new Object())).isSameAs(SchemaObjectType.UNKNOWN); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/definitions/IndexDefinitionUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/definitions/IndexDefinitionUnitTests.java new file mode 100644 index 00000000..80580bd4 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/definitions/IndexDefinitionUnitTests.java @@ -0,0 +1,316 @@ +/* + * Copyright 2017 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.config.schema.definitions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.query.Index; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.data.gemfire.test.support.IOUtils; + +/** + * Unit tests for {@link IndexDefinition}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.query.Index + * @see org.springframework.data.gemfire.IndexType + * @see org.springframework.data.gemfire.config.schema.definitions.IndexDefinition + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class IndexDefinitionUnitTests { + + @Mock + private Index mockIndex; + + @Mock + private Region mockRegion; + + @Before + public void setup() { + when(this.mockIndex.getName()).thenReturn("MockIndex"); + when(this.mockRegion.getFullPath()).thenReturn(String.format("%sMockRegion", Region.SEPARATOR)); + } + + private Index mockIndex(String name, String expression, String fromClause, IndexType type) { + + Index mockIndex = mock(Index.class); + + when(mockIndex.getName()).thenReturn(name); + when(mockIndex.getIndexedExpression()).thenReturn(expression); + when(mockIndex.getFromClause()).thenReturn(fromClause); + when(mockIndex.getType()).thenReturn(type.getGemfireIndexType()); + + return mockIndex; + } + + @Test + public void fromIndexCreatesAnIndexDefinition() { + + Index mockIndex = mockIndex("TestIndex", "id", "/Customers", IndexType.PRIMARY_KEY); + + IndexDefinition indexDefinition = IndexDefinition.from(mockIndex); + + assertThat(indexDefinition).isNotNull(); + assertThat(indexDefinition.getExpression()).isEqualTo(mockIndex.getIndexedExpression()); + assertThat(indexDefinition.getFromClause()).isEqualTo(mockIndex.getFromClause()); + assertThat(indexDefinition.getIndex()).isSameAs(mockIndex); + assertThat(indexDefinition.getIndexType()).isEqualTo(IndexType.valueOf(mockIndex.getType())); + assertThat(indexDefinition.getType()).isEqualTo(SchemaObjectType.INDEX); + } + + @Test(expected = IllegalArgumentException.class) + public void fromNullIndexThrowsIllegalArgumentException() { + + try { + IndexDefinition.from(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Index is required"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void createCallsGemfireAdminOperationsCreateIndexWithThis() { + + GemfireAdminOperations mockAdminOperations = mock(GemfireAdminOperations.class); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(indexDefinition).isNotNull(); + assertThat(indexDefinition.getIndex()).isSameAs(this.mockIndex); + + indexDefinition.create(mockAdminOperations); + + verify(mockAdminOperations, times(1)).createIndex(eq(indexDefinition)); + } + + @Test + @SuppressWarnings("deprecation") + public void asDifferentIndexTypes() { + + when(this.mockIndex.getType()).thenReturn(IndexType.FUNCTIONAL.getGemfireIndexType()); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(indexDefinition).isNotNull(); + assertThat(indexDefinition.getIndex()).isSameAs(this.mockIndex); + assertThat(indexDefinition.getIndexType()).isEqualTo(IndexType.FUNCTIONAL); + assertThat(indexDefinition.as(IndexType.HASH)).isSameAs(indexDefinition); + assertThat(indexDefinition.getIndexType()).isEqualTo(IndexType.HASH); + assertThat(indexDefinition.as(org.apache.geode.cache.query.IndexType.PRIMARY_KEY)).isSameAs(indexDefinition); + assertThat(indexDefinition.getIndexType()).isEqualTo(IndexType.PRIMARY_KEY); + } + + @Test + public void asNullIndexType() { + + when(this.mockIndex.getType()).thenReturn(IndexType.FUNCTIONAL.getGemfireIndexType()); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(indexDefinition).isNotNull(); + assertThat(indexDefinition.getIndex()).isSameAs(this.mockIndex); + assertThat(indexDefinition.getIndexType()).isEqualTo(IndexType.FUNCTIONAL); + assertThat(indexDefinition.as((IndexType) null)).isSameAs(indexDefinition); + assertThat(indexDefinition.getIndexType()).isEqualTo(IndexType.FUNCTIONAL); + assertThat(indexDefinition.as(IndexType.HASH)).isSameAs(indexDefinition); + assertThat(indexDefinition.getIndexType()).isEqualTo(IndexType.HASH); + assertThat(indexDefinition.as((IndexType) null)).isSameAs(indexDefinition); + assertThat(indexDefinition.getIndexType()).isEqualTo(IndexType.FUNCTIONAL); + } + + private void testHavingIllegalExpression(String expression) { + + try { + IndexDefinition.from(this.mockIndex).having(expression); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Expression is required"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void havingExpression() { + + when(this.mockIndex.getIndexedExpression()).thenReturn("id"); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(indexDefinition).isNotNull(); + assertThat(indexDefinition.getIndex()).isSameAs(this.mockIndex); + assertThat(indexDefinition.getExpression()).isEqualTo("id"); + assertThat(indexDefinition.having("age")).isSameAs(indexDefinition); + assertThat(indexDefinition.getExpression()).isEqualTo("age"); + } + + @Test(expected = IllegalArgumentException.class) + public void havingEmptyExpression() { + testHavingIllegalExpression(""); + } + + @Test(expected = IllegalArgumentException.class) + public void havingNoExpression() { + testHavingIllegalExpression(" "); + } + + @Test(expected = IllegalArgumentException.class) + public void havingNullExpression() { + testHavingIllegalExpression(null); + } + + private void testOnIllegalFromClause(String fromClause) { + + try { + IndexDefinition.from(this.mockIndex).on(fromClause); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("From Clause is required"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void onFromClause() { + + when(this.mockIndex.getFromClause()).thenReturn("/People"); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(indexDefinition).isNotNull(); + assertThat(indexDefinition.getFromClause()).isEqualTo(this.mockIndex.getFromClause()); + assertThat(indexDefinition.on("/Customers")).isSameAs(indexDefinition); + assertThat(indexDefinition.getFromClause()).isEqualTo("/Customers"); + } + + @Test(expected = IllegalArgumentException.class) + public void onEmptyFromClause() { + testOnIllegalFromClause(""); + } + + @Test(expected = IllegalArgumentException.class) + public void onNoFromClause() { + testOnIllegalFromClause(" "); + } + + @Test(expected = IllegalArgumentException.class) + public void onNullFromClause() { + testOnIllegalFromClause(null); + } + + @Test + public void onRegion() { + + when(this.mockIndex.getFromClause()).thenReturn("/Mock"); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(indexDefinition).isNotNull(); + assertThat(indexDefinition.getFromClause()).isEqualTo(this.mockIndex.getFromClause()); + assertThat(indexDefinition.on(this.mockRegion)).isSameAs(indexDefinition); + assertThat(indexDefinition.getFromClause()).isEqualTo(this.mockRegion.getFullPath()); + } + + @Test(expected = IllegalArgumentException.class) + public void onNullRegion() { + + try { + IndexDefinition.from(this.mockIndex).on((Region) null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Region is required"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void withName() { + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + assertThat(indexDefinition).isNotNull(); + assertThat(indexDefinition.getName()).isEqualTo("MockIndex"); + assertThat(indexDefinition.with("TestIndex")).isSameAs(indexDefinition); + assertThat(indexDefinition.getName()).isEqualTo("TestIndex"); + assertThat(indexDefinition.with("")).isSameAs(indexDefinition); + assertThat(indexDefinition.getName()).isEqualTo("MockIndex"); + assertThat(indexDefinition.with("UniqueIndex")).isSameAs(indexDefinition); + assertThat(indexDefinition.getName()).isEqualTo("UniqueIndex"); + assertThat(indexDefinition.with(" ")).isSameAs(indexDefinition); + assertThat(indexDefinition.getName()).isEqualTo("MockIndex"); + assertThat(indexDefinition.with("NonUniqueIndex")).isSameAs(indexDefinition); + assertThat(indexDefinition.getName()).isEqualTo("NonUniqueIndex"); + assertThat(indexDefinition.with(null)).isSameAs(indexDefinition); + assertThat(indexDefinition.getName()).isEqualTo("MockIndex"); + } + + @Test + public void serializeDeserializeIsSuccessful() throws ClassNotFoundException, IOException { + + when(this.mockIndex.getIndexedExpression()).thenReturn("age"); + when(this.mockIndex.getFromClause()).thenReturn("/Customers"); + when(this.mockIndex.getType()).thenReturn(IndexType.FUNCTIONAL.getGemfireIndexType()); + + IndexDefinition indexDefinition = IndexDefinition.from(this.mockIndex); + + byte[] indexDefinitionBytes = IOUtils.serializeObject(indexDefinition); + + IndexDefinition deserializedIndexDefinition = IOUtils.deserializeObject(indexDefinitionBytes); + + assertThat(deserializedIndexDefinition).isNotNull(); + assertThat(deserializedIndexDefinition).isNotSameAs(indexDefinition); + assertThat(deserializedIndexDefinition.getIndex()).isInstanceOf(Index.class); + assertThat(deserializedIndexDefinition.getIndex()).isNotSameAs(this.mockIndex); + assertThat(deserializedIndexDefinition.getName()).isEqualTo("MockIndex"); + assertThat(deserializedIndexDefinition.getExpression()).isEqualTo("age"); + assertThat(deserializedIndexDefinition.getFromClause()).isEqualTo("/Customers"); + assertThat(deserializedIndexDefinition.getIndexType()).isEqualTo(IndexType.FUNCTIONAL); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/definitions/RegionDefinitionUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/definitions/RegionDefinitionUnitTests.java new file mode 100644 index 00000000..078818fa --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/definitions/RegionDefinitionUnitTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2017 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.config.schema.definitions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.data.gemfire.test.support.IOUtils; + +/** + * Unit tests for {@link RegionDefinition}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.schema.definitions.RegionDefinition + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class RegionDefinitionUnitTests { + + @Mock + private Region mockRegion; + + @Before + public void setup() { + assertThat(this.mockRegion).isNotNull(); + when(this.mockRegion.getName()).thenReturn("MockRegion"); + } + + @Test + public void fromRegionCreatesRegionDefinition() { + + RegionDefinition regionDefinition = RegionDefinition.from(this.mockRegion); + + assertThat(regionDefinition).isNotNull(); + assertThat(regionDefinition.getName()).isEqualTo(this.mockRegion.getName()); + assertThat(regionDefinition.getRegion()).isSameAs(this.mockRegion); + assertThat(regionDefinition.getRegionShortcut()).isEqualTo(RegionDefinition.DEFAULT_REGION_SHORTCUT); + assertThat(regionDefinition.getType()).isEqualTo(SchemaObjectType.REGION); + } + + @Test(expected = IllegalArgumentException.class) + public void fromNullRegionThrowsIllegalArgumentException() { + + try { + RegionDefinition.from(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Region is required"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void create() { + + GemfireAdminOperations mockAdminOperations = mock(GemfireAdminOperations.class); + + RegionDefinition regionDefinition = RegionDefinition.from(this.mockRegion); + + assertThat(regionDefinition).isNotNull(); + assertThat(regionDefinition.getRegion()).isSameAs(this.mockRegion); + + regionDefinition.create(mockAdminOperations); + + verify(mockAdminOperations, times(1)).createRegion(eq(regionDefinition)); + } + + @Test + public void havingRegionShortcut() { + + RegionDefinition regionDefinition = RegionDefinition.from(this.mockRegion); + + assertThat(regionDefinition).isNotNull(); + assertThat(regionDefinition.getRegion()).isSameAs(this.mockRegion); + assertThat(regionDefinition.getRegionShortcut()).isEqualTo(RegionDefinition.DEFAULT_REGION_SHORTCUT); + assertThat(regionDefinition.having(RegionShortcut.LOCAL)).isSameAs(regionDefinition); + assertThat(regionDefinition.getRegionShortcut()).isEqualTo(RegionShortcut.LOCAL); + assertThat(regionDefinition.having(null)).isSameAs(regionDefinition); + assertThat(regionDefinition.getRegionShortcut()).isEqualTo(RegionDefinition.DEFAULT_REGION_SHORTCUT); + assertThat(regionDefinition.having(RegionShortcut.REPLICATE)).isSameAs(regionDefinition); + assertThat(regionDefinition.getRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE); + } + + @Test + public void withName() { + + RegionDefinition regionDefinition = RegionDefinition.from(this.mockRegion); + + assertThat(regionDefinition).isNotNull(); + assertThat(regionDefinition.getRegion()).isSameAs(this.mockRegion); + assertThat(regionDefinition.getName()).isEqualTo(this.mockRegion.getName()); + assertThat(regionDefinition.with("/Test")).isSameAs(regionDefinition); + assertThat(regionDefinition.getName()).isEqualTo("/Test"); + assertThat(regionDefinition.with(" ")).isSameAs(regionDefinition); + assertThat(regionDefinition.getName()).isEqualTo(this.mockRegion.getName()); + assertThat(regionDefinition.with("/Mock")).isSameAs(regionDefinition); + assertThat(regionDefinition.getName()).isEqualTo("/Mock"); + assertThat(regionDefinition.with(null)).isSameAs(regionDefinition); + assertThat(regionDefinition.getName()).isEqualTo(this.mockRegion.getName()); + } + + @Test + public void serializeDeserializeIsSuccessful() throws IOException, ClassNotFoundException { + + RegionDefinition regionDefinition = RegionDefinition.from(this.mockRegion).having(RegionShortcut.REPLICATE); + + byte[] regionDefinitionBytes = IOUtils.serializeObject(regionDefinition); + + RegionDefinition deserializedRegionDefinition = IOUtils.deserializeObject(regionDefinitionBytes); + + assertThat(deserializedRegionDefinition).isNotNull(); + assertThat(deserializedRegionDefinition.getRegion()).isNull(); + assertThat(deserializedRegionDefinition.getName()).isEqualTo("MockRegion"); + assertThat(deserializedRegionDefinition.getRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/support/ClientRegionCollectorUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/support/ClientRegionCollectorUnitTests.java new file mode 100644 index 00000000..a95a2f0b --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/support/ClientRegionCollectorUnitTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; + +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.context.ApplicationContext; + +/** + * Unit tests for {@link ClientRegionCollector}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.schema.support.ClientRegionCollector + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class ClientRegionCollectorUnitTests { + + @Mock + private ApplicationContext mockApplicationContext; + + @Mock + private GemFireCache mockCache; + + private ClientRegionCollector clientRegionCollector = new ClientRegionCollector(); + + @SuppressWarnings("unchecked") + private Region mockRegion(String name) { + + Region mockRegion = mock(Region.class, name); + + when(mockRegion.getName()).thenReturn(name); + + return mockRegion; + } + + private Map asMap(Region... regions) { + return stream(regions).collect(Collectors.toMap(Region::getName, Function.identity())); + } + + @Test + public void collectClientRegionsFromApplicationContext() { + + Region mockRegionOne = mockRegion("MockRegionOne"); + Region mockRegionTwo = mockRegion("MockRegionTwo"); + Region mockRegionThree = mockRegion("MockRegionThree"); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + when(mockRegionAttributes.getPoolName()).thenReturn("TestPool"); + when(mockRegionTwo.getAttributes()).thenReturn(mockRegionAttributes); + + Map regionBeans = asMap(mockRegionOne, mockRegionTwo, mockRegionThree); + + when(this.mockApplicationContext.getBeansOfType(eq(Region.class))).thenReturn(regionBeans); + + Set clientRegions = this.clientRegionCollector.collectFrom(this.mockApplicationContext); + + assertThat(clientRegions).isNotNull(); + assertThat(clientRegions).hasSize(1); + assertThat(clientRegions).containsAll(asSet(mockRegionTwo)); + + verify(this.mockApplicationContext, times(1)).getBeansOfType(eq(Region.class)); + } + + @Test + public void collectClientRegionsFromApplicationContextWithNoClientRegions() { + + Region mockRegionOne = mockRegion("MockRegionOne"); + Region mockRegionTwo = mockRegion("MockRegionTwo"); + Region mockRegionThree = mockRegion("MockRegionThree"); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + when(mockRegionAttributes.getPoolName()).thenReturn(" "); + when(mockRegionOne.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegionThree.getAttributes()).thenReturn(mockRegionAttributes); + + Map regionBeans = asMap(mockRegionOne, mockRegionTwo, mockRegionThree); + + when(this.mockApplicationContext.getBeansOfType(eq(Region.class))).thenReturn(regionBeans); + + Set clientRegions = this.clientRegionCollector.collectFrom(this.mockApplicationContext); + + assertThat(clientRegions).isNotNull(); + assertThat(clientRegions).isEmpty(); + + verify(this.mockApplicationContext, times(1)).getBeansOfType(eq(Region.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void collectClientRegionsFromGemFireCache() { + + Region mockRegionOne = mockRegion("MockRegionOne"); + Region mockRegionTwo = mockRegion("MockRegionTwo"); + Region mockRegionThree = mockRegion("MockRegionThree"); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + when(mockRegionAttributes.getPoolName()).thenReturn("TestPool"); + when(mockRegionOne.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegionTwo.getAttributes()).thenReturn(mockRegionAttributes); + + when(this.mockCache.rootRegions()).thenReturn(asSet(mockRegionOne, mockRegionTwo, mockRegionThree)); + + Set clientRegions = this.clientRegionCollector.collectFrom(this.mockCache); + + assertThat(clientRegions).isNotNull(); + assertThat(clientRegions).hasSize(2); + assertThat(clientRegions).containsAll(asSet(mockRegionOne, mockRegionTwo)); + + verify(this.mockCache, times(1)).rootRegions(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectCollectorUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectCollectorUnitTests.java new file mode 100644 index 00000000..300d50cd --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectCollectorUnitTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; + +import java.util.Collections; + +import org.apache.geode.cache.GemFireCache; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.config.schema.SchemaObjectCollector; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Unit tests for {@link ComposableSchemaObjectCollector}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.schema.support.ComposableSchemaObjectCollector + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class ComposableSchemaObjectCollectorUnitTests { + + @Mock + private ApplicationContext mockApplicationContext; + + @Mock + private GemFireCache mockCache; + + @Mock + private SchemaObjectCollector mockSchemaObjectCollectorOne; + + @Mock + private SchemaObjectCollector mockSchemaObjectCollectorTwo; + + private Iterable emptyIterable() { + return Collections::emptyIterator; + } + + @Test + public void composeArrayWithNoElements() { + assertThat(ComposableSchemaObjectCollector.compose()).isNull(); + } + + @Test + public void composeArrayWithOneElement() { + assertThat(ComposableSchemaObjectCollector.compose(this.mockSchemaObjectCollectorOne)) + .isSameAs(this.mockSchemaObjectCollectorOne); + } + + @Test + public void composeArrayWithTwoElements() { + + SchemaObjectCollector composedSchemaObjectCollector = ComposableSchemaObjectCollector.compose( + this.mockSchemaObjectCollectorOne, this.mockSchemaObjectCollectorTwo); + + assertThat(composedSchemaObjectCollector).isInstanceOf(ComposableSchemaObjectCollector.class); + assertThat((ComposableSchemaObjectCollector) composedSchemaObjectCollector).hasSize(2); + assertThat((ComposableSchemaObjectCollector) composedSchemaObjectCollector) + .containsAll(asSet(this.mockSchemaObjectCollectorOne, this.mockSchemaObjectCollectorTwo)); + + } + + @Test + public void composeIterableWithNoElements() { + assertThat(ComposableSchemaObjectCollector.compose(emptyIterable())).isNull(); + } + + @Test + public void composableIterableWithOneElement() { + assertThat(ComposableSchemaObjectCollector.compose(Collections.singleton(this.mockSchemaObjectCollectorTwo))) + .isSameAs(this.mockSchemaObjectCollectorTwo); + } + + @Test + public void composeIterableWithTwoElements() { + + SchemaObjectCollector composedSchemaObjectCollector = ComposableSchemaObjectCollector.compose( + asSet(this.mockSchemaObjectCollectorOne, this.mockSchemaObjectCollectorTwo)); + + assertThat(composedSchemaObjectCollector).isInstanceOf(ComposableSchemaObjectCollector.class); + assertThat((ComposableSchemaObjectCollector) composedSchemaObjectCollector) + .containsAll(asSet(this.mockSchemaObjectCollectorOne, this.mockSchemaObjectCollectorTwo)); + } + + @Test + @SuppressWarnings("unchecked") + public void collectFromApplicationContext() { + + SchemaObject region = SchemaObject.of(SchemaObjectType.REGION); + SchemaObject index = SchemaObject.of(SchemaObjectType.INDEX); + SchemaObject diskStore = SchemaObject.of(SchemaObjectType.DISK_STORE); + + when(this.mockSchemaObjectCollectorOne.collectFrom(any(ApplicationContext.class))) + .thenReturn(asSet(region, index)); + + when(this.mockSchemaObjectCollectorTwo.collectFrom(any(ApplicationContext.class))) + .thenReturn(asSet(diskStore)); + + SchemaObjectCollector composedSchemaObjectCollector = ComposableSchemaObjectCollector.compose( + asSet(this.mockSchemaObjectCollectorOne, this.mockSchemaObjectCollectorTwo)); + + assertThat(composedSchemaObjectCollector).isNotNull(); + + Iterable schemaObjects = composedSchemaObjectCollector.collectFrom(this.mockApplicationContext); + + assertThat(schemaObjects).isNotNull(); + assertThat(schemaObjects).hasSize(3); + assertThat(schemaObjects).contains(region, index, diskStore); + + verify(this.mockSchemaObjectCollectorOne, times(1)) + .collectFrom(eq(this.mockApplicationContext)); + + verify(this.mockSchemaObjectCollectorTwo, times(1)) + .collectFrom(eq(this.mockApplicationContext)); + } + + @Test + @SuppressWarnings("unchecked") + public void collectFromGemFireCache() { + + SchemaObject asyncEventQueue = SchemaObject.of(SchemaObjectType.ASYNC_EVENT_QUEUE); + SchemaObject gatewayReceiver = SchemaObject.of(SchemaObjectType.GATEWAY_RECEIVER); + SchemaObject gatewaySender = SchemaObject.of(SchemaObjectType.GATEWAY_SENDER); + + when(this.mockSchemaObjectCollectorOne.collectFrom(any(GemFireCache.class))) + .thenReturn(asSet(gatewayReceiver, gatewaySender)); + + when(this.mockSchemaObjectCollectorTwo.collectFrom(any(GemFireCache.class))) + .thenReturn(asSet(asyncEventQueue)); + + SchemaObjectCollector composedSchemaObjectCollector = ComposableSchemaObjectCollector.compose( + asSet(this.mockSchemaObjectCollectorOne, this.mockSchemaObjectCollectorTwo)); + + assertThat(composedSchemaObjectCollector).isNotNull(); + + Iterable schemaObjects = composedSchemaObjectCollector.collectFrom(this.mockCache); + + assertThat(schemaObjects).isNotNull(); + assertThat(schemaObjects).hasSize(3); + assertThat(schemaObjects).contains(gatewayReceiver, gatewaySender, asyncEventQueue); + + verify(this.mockSchemaObjectCollectorOne, times(1)).collectFrom(eq(this.mockCache)); + + verify(this.mockSchemaObjectCollectorTwo, times(1)).collectFrom(eq(this.mockCache)); + } + + @RequiredArgsConstructor(staticName = "of") + static class SchemaObject { + @NonNull SchemaObjectType type; + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectDefinerUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectDefinerUnitTests.java new file mode 100644 index 00000000..4b7b2e31 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/support/ComposableSchemaObjectDefinerUnitTests.java @@ -0,0 +1,169 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; + +import org.apache.geode.cache.Region; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.config.schema.SchemaObjectDefiner; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; + +/** + * Unit tests for {@link ComposableSchemaObjectDefiner}. + * + * @author John Blum + * @see org.junit.Test + * @see SchemaObjectDefiner + * @see ComposableSchemaObjectDefiner + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class ComposableSchemaObjectDefinerUnitTests { + + @Mock + private SchemaObjectDefiner mockHandlerOne; + + @Mock + private SchemaObjectDefiner mockHandlerTwo; + + private Iterable emptyIterable() { + return Collections::emptyIterator; + } + + @Test + public void composeArrayWithNoElements() { + assertThat(ComposableSchemaObjectDefiner.compose()).isNull(); + } + + @Test + public void composeArrayWithOneElement() { + assertThat(ComposableSchemaObjectDefiner.compose(this.mockHandlerOne)).isSameAs(this.mockHandlerOne); + } + + @Test + public void composeArrayWithTwoElements() { + + SchemaObjectDefiner handler = + ComposableSchemaObjectDefiner.compose(this.mockHandlerOne, this.mockHandlerTwo); + + assertThat(handler).isInstanceOf(ComposableSchemaObjectDefiner.class); + assertThat((ComposableSchemaObjectDefiner) handler) + .containsAll(asSet(this.mockHandlerOne, this.mockHandlerTwo)); + } + + @Test + public void composeIterableWithNoElements() { + assertThat(ComposableSchemaObjectDefiner.compose(emptyIterable())).isNull(); + } + + @Test + public void composeIterableWithOneElement() { + assertThat(ComposableSchemaObjectDefiner.compose(Collections.singleton(this.mockHandlerTwo))) + .isSameAs(this.mockHandlerTwo); + } + + @Test + public void composeIterableWithTwoElements() { + + SchemaObjectDefiner handler = + ComposableSchemaObjectDefiner.compose(asSet(this.mockHandlerOne, this.mockHandlerTwo)); + + assertThat(handler).isInstanceOf(ComposableSchemaObjectDefiner.class); + assertThat((ComposableSchemaObjectDefiner) handler) + .containsAll(asSet(this.mockHandlerOne, this.mockHandlerTwo)); + } + + @Test + public void getSchemaObjectTypesIsComposed() { + + when(this.mockHandlerOne.getSchemaObjectTypes()) + .thenReturn(asSet(SchemaObjectType.INDEX, SchemaObjectType.LUCENE_INDEX)); + + when(this.mockHandlerTwo.getSchemaObjectTypes()) + .thenReturn(Collections.singleton(SchemaObjectType.DISK_STORE)); + + SchemaObjectDefiner handler = + ComposableSchemaObjectDefiner.compose(this.mockHandlerOne, this.mockHandlerTwo); + + assertThat(handler).isNotNull(); + + assertThat(handler.getSchemaObjectTypes()).containsAll( + Arrays.asList(SchemaObjectType.INDEX, SchemaObjectType.LUCENE_INDEX, SchemaObjectType.DISK_STORE)); + } + + @Test + public void defineReturnsRegionDefinition() { + + Region mockRegion = mock(Region.class); + + when(mockRegion.getName()).thenReturn("MockRegion"); + + Optional regionDefinition = Optional.of(RegionDefinition.from(mockRegion)); + + when(this.mockHandlerTwo.canDefine(any(Region.class))).thenReturn(true); + when(this.mockHandlerTwo.define(any(Region.class))).thenAnswer(invocation -> regionDefinition); + + SchemaObjectDefiner handler = + ComposableSchemaObjectDefiner.compose(this.mockHandlerOne, this.mockHandlerTwo); + + assertThat(handler).isNotNull(); + assertThat(handler.define(mockRegion)).isEqualTo(regionDefinition); + + verify(this.mockHandlerOne, atMost(1)).canDefine(eq(mockRegion)); + verify(this.mockHandlerOne, never()).define(any()); + verify(this.mockHandlerTwo, times(1)).canDefine(eq(mockRegion)); + verify(this.mockHandlerTwo, times(1)).define(eq(mockRegion)); + } + + @Test + public void defineReturnsEmptyOptional() { + + Object object = new Object(); + + when(this.mockHandlerOne.canDefine(any(Object.class))).thenReturn(false); + when(this.mockHandlerTwo.canDefine(any(Object.class))).thenReturn(false); + + SchemaObjectDefiner handler = + ComposableSchemaObjectDefiner.compose(this.mockHandlerOne, this.mockHandlerTwo); + + assertThat(handler).isNotNull(); + assertThat(handler.define(object).isPresent()).isFalse(); + + verify(this.mockHandlerOne, times(1)).canDefine(eq(object)); + verify(this.mockHandlerOne, never()).define(any()); + verify(this.mockHandlerTwo, times(1)).canDefine(eq(object)); + verify(this.mockHandlerTwo, never()).define(any()); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/support/IndexCollectorUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/support/IndexCollectorUnitTests.java new file mode 100644 index 00000000..6e5c5bf9 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/support/IndexCollectorUnitTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.query.Index; +import org.apache.geode.cache.query.QueryService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.context.ApplicationContext; + +/** + * Unit tests for {@link IndexCollector}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class IndexCollectorUnitTests { + + @Mock + private ApplicationContext mockApplicationContext; + + @Mock + private GemFireCache mockCache; + + private IndexCollector indexCollector = new IndexCollector(); + + @Mock + private QueryService mockQueryService; + + @Before + public void setup() { + when(this.mockCache.getQueryService()).thenReturn(this.mockQueryService); + } + + private Index mockIndex(String name) { + + Index mockIndex = mock(Index.class, name); + + when(mockIndex.getName()).thenReturn(name); + + return mockIndex; + } + + private Map asMap(Index... indexes) { + return stream(indexes).collect(Collectors.toMap(Index::getName, Function.identity())); + } + + @Test + public void collectIndexesFromApplicationContext() { + + Index mockIndexOne = mockIndex("MockIndexOne"); + Index mockIndexTwo = mockIndex("MockIndexTwo"); + + Map indexBeans = asMap(mockIndexOne, mockIndexTwo); + + when(this.mockApplicationContext.getBeansOfType(eq(Index.class))).thenReturn(indexBeans); + + Set indexes = this.indexCollector.collectFrom(this.mockApplicationContext); + + assertThat(indexes).isNotNull(); + assertThat(indexes).hasSize(2); + assertThat(indexes).containsAll(asSet(mockIndexOne, mockIndexTwo)); + + verify(this.mockApplicationContext, times(1)).getBeansOfType(eq(Index.class)); + } + + @Test + public void collectIndexesFromApplicationContextWhenNoBeansOfTypeIndexExist() { + + when(this.mockApplicationContext.getBeansOfType(eq(Index.class))).thenReturn(Collections.emptyMap()); + + Set indexes = this.indexCollector.collectFrom(this.mockApplicationContext); + + assertThat(indexes).isNotNull(); + assertThat(indexes).isEmpty(); + + verify(this.mockApplicationContext, times(1)).getBeansOfType(eq(Index.class)); + } + + @Test + public void collectIndexesFromGemFireCache() { + + Index mockIndexOne = mockIndex("MockIndexOne"); + Index mockIndexTwo = mockIndex("MockIndexTwo"); + + when(this.mockQueryService.getIndexes()).thenReturn(asSet(mockIndexOne, mockIndexTwo)); + + Set indexes = this.indexCollector.collectFrom(this.mockCache); + + assertThat(indexes).isNotNull(); + assertThat(indexes).hasSize(2); + assertThat(indexes).containsAll(asSet(mockIndexOne, mockIndexTwo)); + + verify(this.mockCache, times(1)).getQueryService(); + verify(this.mockQueryService, times(1)).getIndexes(); + } + + @Test + public void collectIndexesFromGemFireCacheWhenNoIndexesExist() { + + when(this.mockQueryService.getIndexes()).thenReturn(Collections.emptySet()); + + Set indexes = this.indexCollector.collectFrom(this.mockCache); + + assertThat(indexes).isNotNull(); + assertThat(indexes).isEmpty(); + + verify(this.mockCache, times(1)).getQueryService(); + verify(this.mockQueryService, times(1)).getIndexes(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/support/IndexDefinerUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/support/IndexDefinerUnitTests.java new file mode 100644 index 00000000..8395f867 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/support/IndexDefinerUnitTests.java @@ -0,0 +1,127 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.query.Index; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; + +/** + * Unit tests for {@link IndexDefiner}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.apache.geode.cache.query.Index + * @see org.springframework.data.gemfire.IndexType + * @see org.springframework.data.gemfire.config.schema.definitions.IndexDefinition + * @see IndexDefiner + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class IndexDefinerUnitTests { + + @Mock + private Index mockIndex; + + @Mock + private Region mockRegion; + + private IndexDefiner indexInstanceHandler = new IndexDefiner(); + + @Before + @SuppressWarnings("unchecked") + public void setup() { + when(this.mockIndex.getName()).thenReturn("MockIndex"); + when(this.mockIndex.getIndexedExpression()).thenReturn("testExpression"); + when(this.mockIndex.getFromClause()).thenReturn("/TestFromClause"); + when(this.mockIndex.getType()).thenReturn(IndexType.HASH.getGemfireIndexType()); + } + + @Test + public void canHandleIndexInstanceIsTrue() { + assertThat(this.indexInstanceHandler.canDefine(this.mockIndex)).isTrue(); + } + + @Test + public void canHandleNullInstanceIsFalse() { + assertThat(this.indexInstanceHandler.canDefine((Object) null)).isFalse(); + } + + @Test + public void canHandleRegionInstanceIsFalse() { + assertThat(this.indexInstanceHandler.canDefine(this.mockRegion)).isFalse(); + } + + @Test + public void canHandleIndexTypeIsTrue() { + assertThat(this.indexInstanceHandler.canDefine(Index.class)).isTrue(); + } + + @Test + public void canHandleNullTypeIsFalse() { + assertThat(this.indexInstanceHandler.canDefine((Class) null)).isFalse(); + } + + @Test + public void canHandleRegionTypeIsFalse() { + assertThat(this.indexInstanceHandler.canDefine(Region.class)).isFalse(); + } + + @Test + public void canHandleIndexSchemaObjectTypeIsTrue() { + assertThat(this.indexInstanceHandler.canDefine(SchemaObjectType.INDEX)).isTrue(); + } + + @Test + public void canHandleNullSchemaObjectTypeIsFalse() { + assertThat(this.indexInstanceHandler.canDefine((SchemaObjectType) null)).isFalse(); + } + + @Test + public void canHandleRegionSchemaObjectTypeIsFalse() { + assertThat(this.indexInstanceHandler.canDefine(SchemaObjectType.REGION)).isFalse(); + } + + @Test + public void defineForIndexObject() { + + IndexDefinition indexDefinition = this.indexInstanceHandler.define(this.mockIndex).orElse(null); + + assertThat(indexDefinition).isNotNull(); + assertThat(indexDefinition.getExpression()).isEqualTo("testExpression"); + assertThat(indexDefinition.getFromClause()).isEqualTo("/TestFromClause"); + assertThat(indexDefinition.getIndexType()).isEqualTo(IndexType.HASH); + assertThat(indexDefinition.getType()).isEqualTo(SchemaObjectType.INDEX); + } + + @Test + public void defineForRegionObject() { + assertThat(this.indexInstanceHandler.define(this.mockRegion).isPresent()).isFalse(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/support/RegionCollectorUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/support/RegionCollectorUnitTests.java new file mode 100644 index 00000000..f2a20423 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/support/RegionCollectorUnitTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.util.CollectionUtils.asSet; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.context.ApplicationContext; + +/** + * Unit tests for {@link RegionCollector}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.schema.support.RegionCollector + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class RegionCollectorUnitTests { + + @Mock + private ApplicationContext mockApplicationContext; + + @Mock + private GemFireCache mockCache; + + private RegionCollector regionCollector = new RegionCollector(); + + @SuppressWarnings("unchecked") + private Region mockRegion(String name) { + + Region mockRegion = mock(Region.class, name); + + when(mockRegion.getName()).thenReturn(name); + + return mockRegion; + } + + private Map asMap(Region... regions) { + return stream(regions).collect(Collectors.toMap(Region::getName, Function.identity())); + } + + @Test + public void collectRegionsFromApplicationContext() { + + Region mockRegionOne = mockRegion("MockRegionOne"); + Region mockRegionTwo = mockRegion("MockRegionTwo"); + + Map regionBeans = asMap(mockRegionOne, mockRegionTwo); + + when(this.mockApplicationContext.getBeansOfType(eq(Region.class))).thenReturn(regionBeans); + + Set actualRegions = this.regionCollector.collectFrom(this.mockApplicationContext); + + assertThat(actualRegions).isNotNull(); + assertThat(actualRegions).hasSize(2); + assertThat(actualRegions).containsAll(asSet(mockRegionOne, mockRegionTwo)); + + verify(this.mockApplicationContext, times(1)).getBeansOfType(eq(Region.class)); + } + + @Test + public void collectRegionsFromApplicationContextWhenNoBeansOfTypeRegionExist() { + + when(this.mockApplicationContext.getBeansOfType(eq(Region.class))).thenReturn(Collections.emptyMap()); + + Set actualRegions = this.regionCollector.collectFrom(this.mockApplicationContext); + + assertThat(actualRegions).isNotNull(); + assertThat(actualRegions).isEmpty(); + + verify(this.mockApplicationContext, times(1)).getBeansOfType(eq(Region.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void collectRegionsFromGemFireCache() { + + Region mockRegionOne = mockRegion("MockRegionOne"); + Region mockRegionTwo = mockRegion("MockRegionTwo"); + + when(this.mockCache.rootRegions()).thenReturn(asSet(mockRegionOne, mockRegionTwo)); + + Set actualRegions = this.regionCollector.collectFrom(this.mockCache); + + assertThat(actualRegions).isNotNull(); + assertThat(actualRegions).hasSize(2); + assertThat(actualRegions).containsAll(asSet(mockRegionOne, mockRegionTwo)); + + verify(this.mockCache, times(1)).rootRegions(); + } + + @Test + @SuppressWarnings("unchecked") + public void collectRegionsFromGemFireCacheWhenNoRootRegionsExist() { + + when(this.mockCache.rootRegions()).thenReturn(Collections.emptySet()); + + Set actualRegions = this.regionCollector.collectFrom(this.mockCache); + + assertThat(actualRegions).isNotNull(); + assertThat(actualRegions).isEmpty(); + + verify(this.mockCache, times(1)).rootRegions(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/schema/support/RegionDefinerUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/schema/support/RegionDefinerUnitTests.java new file mode 100644 index 00000000..ad5033ae --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/schema/support/RegionDefinerUnitTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2017 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.config.schema.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.query.Index; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.gemfire.config.schema.SchemaObjectType; +import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; + +/** + * Unit tests for {@link RegionDefiner}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.apache.geode.cache.Region + * @see org.springframework.data.gemfire.config.schema.definitions.RegionDefinition + * @since 2.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class RegionDefinerUnitTests { + + @Mock + private Index mockIndex; + + @Mock + private Region mockRegion; + + private RegionDefiner regionInstanceHandler = new RegionDefiner(); + + @Before + public void setup() { + when(this.mockRegion.getName()).thenReturn("MockRegion"); + } + + @Test + public void canHandleIndexInstanceIsFalse() { + assertThat(this.regionInstanceHandler.canDefine(this.mockIndex)).isFalse(); + } + + @Test + public void canHandleNullInstanceIsFalse() { + assertThat(this.regionInstanceHandler.canDefine((Object) null)).isFalse(); + } + + @Test + public void canHandleRegionInstanceIsTrue() { + assertThat(this.regionInstanceHandler.canDefine(this.mockRegion)).isTrue(); + } + + @Test + public void canHandleIndexTypeIsFalse() { + assertThat(this.regionInstanceHandler.canDefine(Index.class)).isFalse(); + } + + @Test + public void canHandleNullTypeIsFalse() { + assertThat(this.regionInstanceHandler.canDefine((Class) null)).isFalse(); + } + + @Test + public void canHandleRegionTypeIsTrue() { + assertThat(this.regionInstanceHandler.canDefine(Region.class)).isTrue(); + } + + @Test + public void canHandleIndexSchemaObjectTypeIsFalse() { + assertThat(this.regionInstanceHandler.canDefine(SchemaObjectType.INDEX)).isFalse(); + } + + @Test + public void canHandleNullSchemaObjectTypeIsFalse() { + assertThat(this.regionInstanceHandler.canDefine((SchemaObjectType) null)).isFalse(); + } + + @Test + public void canHandleRegionSchemaObjectTypeIsTrue() { + assertThat(this.regionInstanceHandler.canDefine(SchemaObjectType.REGION)).isTrue(); + } + + @Test + @SuppressWarnings("unchecked") + public void defineClientRegion() { + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + when(this.mockRegion.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegionAttributes.getPoolName()).thenReturn("TestPool"); + + RegionDefinition regionDefinition = this.regionInstanceHandler.define(this.mockRegion).orElse(null); + + assertThat(regionDefinition).isNotNull(); + assertThat(regionDefinition.getName()).isEqualTo(this.mockRegion.getName()); + assertThat(regionDefinition.getRegionShortcut()).isEqualTo(RegionShortcut.PARTITION); + assertThat(regionDefinition.getType()).isEqualTo(SchemaObjectType.REGION); + } + + @Test + @SuppressWarnings("unchecked") + public void definePeerRegion() { + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + when(this.mockRegion.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegionAttributes.getPoolName()).thenReturn(" "); + + assertThat(this.regionInstanceHandler.define(this.mockRegion).isPresent()).isFalse(); + } + + @Test + public void defineWithNull() { + assertThat(this.regionInstanceHandler.define(null).isPresent()).isFalse(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/test/support/IOUtils.java b/src/test/java/org/springframework/data/gemfire/test/support/IOUtils.java index b439fec4..546161c3 100644 --- a/src/test/java/org/springframework/data/gemfire/test/support/IOUtils.java +++ b/src/test/java/org/springframework/data/gemfire/test/support/IOUtils.java @@ -16,8 +16,13 @@ package org.springframework.data.gemfire.test.support; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.util.logging.Level; import java.util.logging.Logger; @@ -35,19 +40,57 @@ public abstract class IOUtils { /* (non-Javadoc) */ public static boolean close(Closeable closeable) { + if (closeable != null) { try { closeable.close(); return true; } - catch (IOException ignore) { + catch (IOException cause) { if (log.isLoggable(Level.FINE)) { log.fine(String.format("Failed to close the Closeable object (%1$s) due to an I/O error:%n%2$s", - closeable, ThrowableUtils.toString(ignore))); + closeable, ThrowableUtils.toString(cause))); } } } return false; } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + public static T deserializeObject(byte[] objectBytes) throws IOException, ClassNotFoundException { + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(objectBytes); + + ObjectInputStream objectInputStream = null; + + try { + objectInputStream = new ObjectInputStream(byteArrayInputStream); + + return (T) objectInputStream.readObject(); + } + finally { + IOUtils.close(objectInputStream); + } + } + + /* (non-Javadoc) */ + public static byte[] serializeObject(Serializable obj) throws IOException { + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + ObjectOutputStream objectOutputStream = null; + + try { + objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); + objectOutputStream.writeObject(obj); + objectOutputStream.flush(); + + return byteArrayOutputStream.toByteArray(); + } + finally { + IOUtils.close(objectOutputStream); + } + } }