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 extends Annotation> 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 extends SchemaObjectDefinition> 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