SGF-674 - Add EnableClusterConfiguration annotation to push cluster configuration meta-data from the client to the server.

This commit is contained in:
John Blum
2017-08-05 21:43:56 -07:00
parent cbc655cd3d
commit b2b826a1e5
54 changed files with 6129 additions and 107 deletions

View File

@@ -21,23 +21,23 @@ import java.util.concurrent.ConcurrentMap;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.Region;
import org.apache.geode.internal.GemFireVersion;
import org.springframework.data.gemfire.util.CacheUtils;
import org.springframework.data.gemfire.util.RegionUtils;
import org.springframework.util.ClassUtils;
/**
* 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 APACHE_GEODE_NAME = "Apache Geode";
public final static String GEMFIRE_NAME = apacheGeodeProductName();
public final static String GEMFIRE_VERSION = apacheGeodeVersion();
public final static String UNKNOWN = "unknown";

View File

@@ -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<String> regionNames() {
try {
return execute(new ListRegionsOnServerFunction());

View File

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

View File

@@ -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<String> 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<String> 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<RegionDefinition> 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<SchemaObjectDefinition> 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<IndexDefinition> 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<SchemaObjectDefinition> diskStoreDefinitions) {
nullSafeIterable(diskStoreDefinitions).forEach(this::createDiskStore);
}
}

View File

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

View File

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

View File

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

View File

@@ -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<String> 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<String> 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> T execute(Function gemfireFunction, Object... arguments) {
return newGemfireFunctionOperations().executeAndExtract(gemfireFunction, arguments);
}
/* (non-Javadoc) */
<T> 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();
}
}

View File

@@ -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<String, Object> 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<MultiValueMap<String, Object>> requestEntity =
new RequestEntity<>(requestParameters, httpHeaders, HttpMethod.POST, resolveCreateIndexUri());
ResponseEntity<String> 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<String, Object> 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<MultiValueMap<String, Object>> requestEntity =
new RequestEntity<>(requestParameters, httpHeaders, HttpMethod.POST, resolveCreateRegionUri());
ResponseEntity<String> 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"));
}
}

View File

@@ -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<String> 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<Integer> 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<Boolean> 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<RegionShortcut> 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()
);
}
}

View File

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

View File

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

View File

@@ -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<T> {
/**
* 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<T> 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<T> collectFrom(GemFireCache gemfireCache) {
return Collections.emptySet();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Region> collectFrom(ApplicationContext applicationContext) {
return onlyClientRegions(super.collectFrom(applicationContext));
}
@Override
public Set<Region> collectFrom(GemFireCache gemfireCache) {
return onlyClientRegions(super.collectFrom(gemfireCache));
}
private Set<Region> onlyClientRegions(Set<Region> regions) {
return nullSafeSet(regions).stream().filter(RegionUtils::isClient).collect(Collectors.toSet());
}
}

View File

@@ -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<Object>, Iterable<SchemaObjectCollector<?>> {
private final List<SchemaObjectCollector<?>> schemaObjectCollectors;
@Nullable
@SuppressWarnings("unchecked")
public static SchemaObjectCollector<?> compose(SchemaObjectCollector<?>... schemaObjectCollectors) {
return compose(Arrays.asList(nullSafeArray(schemaObjectCollectors, SchemaObjectCollector.class)));
}
@Nullable
public static SchemaObjectCollector<?> compose(Iterable<SchemaObjectCollector<?>> schemaObjectCollectors) {
List<SchemaObjectCollector<?>> 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<SchemaObjectCollector<?>> schemaObjectCollectors) {
this.schemaObjectCollectors = Collections.unmodifiableList(schemaObjectCollectors);
}
@Override
public Iterable<Object> collectFrom(ApplicationContext applicationContext) {
return collectFrom(collector -> collector.collectFrom(applicationContext));
}
@Override
public Iterable<Object> collectFrom(GemFireCache gemfireCache) {
return collectFrom(collector -> collector.collectFrom(gemfireCache));
}
private Iterable<Object> collectFrom(Function<SchemaObjectCollector<?>, Iterable<?>> schemaObjectSource) {
return this.schemaObjectCollectors.stream()
.flatMap(collector -> stream(schemaObjectSource.apply(collector).spliterator(), false))
.collect(Collectors.toList());
}
@Override
public Iterator<SchemaObjectCollector<?>> iterator() {
return Collections.unmodifiableList(this.schemaObjectCollectors).iterator();
}
}

View File

@@ -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<SchemaObjectDefiner> {
@Nullable
public static SchemaObjectDefiner compose(SchemaObjectDefiner... schemaObjectDefiners) {
return compose(Arrays.asList(nullSafeArray(schemaObjectDefiners, SchemaObjectDefiner.class)));
}
@Nullable
public static SchemaObjectDefiner compose(Iterable<SchemaObjectDefiner> schemaObjectDefiners) {
Set<SchemaObjectDefiner> 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<SchemaObjectDefiner> 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<SchemaObjectDefiner> schemaObjectDefiners) {
this.schemaObjectDefiners = Collections.unmodifiableSet(schemaObjectDefiners);
}
@Override
public Set<SchemaObjectType> getSchemaObjectTypes() {
return this.schemaObjectDefiners.stream()
.flatMap(it -> it.getSchemaObjectTypes().stream())
.collect(Collectors.toSet());
}
@Override
public Optional<? extends SchemaObjectDefinition> define(Object schemaObject) {
return this.schemaObjectDefiners.stream()
.filter(it -> it.canDefine(schemaObject)).findAny()
.map(it -> it.define(schemaObject).orElse(null));
}
@Override
public Iterator<SchemaObjectDefiner> iterator() {
return Collections.unmodifiableSet(this.schemaObjectDefiners).iterator();
}
}

View File

@@ -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<Index> {
@Override
public Set<Index> collectFrom(ApplicationContext applicationContext) {
return applicationContext.getBeansOfType(Index.class).values().stream().collect(Collectors.toSet());
}
@Override
public Set<Index> collectFrom(GemFireCache gemfireCache) {
return gemfireCache.getQueryService().getIndexes().stream().collect(Collectors.toSet());
}
}

View File

@@ -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<SchemaObjectType> getSchemaObjectTypes() {
return asSet(SchemaObjectType.INDEX);
}
@Override
public Optional<IndexDefinition> define(Object schemaObject) {
return Optional.ofNullable(schemaObject).filter(this::canDefine).map(it -> IndexDefinition.from((Index) it));
}
}

View File

@@ -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<Region> {
@Override
public Set<Region> collectFrom(ApplicationContext applicationContext) {
return applicationContext.getBeansOfType(Region.class).values().stream().collect(Collectors.toSet());
}
@Override
public Set<Region> collectFrom(GemFireCache gemfireCache) {
return gemfireCache.rootRegions().stream().collect(Collectors.toSet());
}
}

View File

@@ -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<SchemaObjectType> getSchemaObjectTypes() {
return asSet(SchemaObjectType.REGION);
}
@Override
public Optional<RegionDefinition> define(Object schemaObject) {
return Optional.ofNullable(schemaObject)
.filter(this::canDefine)
.map(it -> (Region) it)
.filter(RegionUtils::isClient)
.map(it -> RegionDefinition.from(it).having(getRegionShortcut()));
}
}

View File

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

View File

@@ -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<String, Object> 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<? extends Annotation> 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<String> 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<String, Object> 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<Class<?>> 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;
}
}

View File

@@ -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<Object> resultSender, Object result) {
if (result == null) {
resultSender.lastResult(null);
}
@@ -139,5 +151,4 @@ public class PojoFunctionWrapper implements Function {
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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 <T> Iterable<T> execute(Function function, Object... args);
<T> Iterable<T> 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 <T> Iterable<T> execute(String functionId, Object... args);
<T> Iterable<T> 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> T executeAndExtract(Function function, Object... args);
<T> 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> T executeAndExtract(String functionId, Object... args);
<T> 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> T execute(GemfireFunctionCallback<T> callback);
<T> T execute(GemfireFunctionCallback<T> callback);
}

View File

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

View File

@@ -43,6 +43,7 @@ public abstract class SpringUtils {
/* (non-Javadoc) */
public static BeanDefinition addDependsOn(BeanDefinition bean, String... beanNames) {
List<String> dependsOnList = new ArrayList<>();
Collections.addAll(dependsOnList, nullSafeArray(bean.getDependsOn(), String.class));