diff --git a/docs/src/info/changelog.txt b/docs/src/info/changelog.txt index e226a2dd..e732d5b9 100644 --- a/docs/src/info/changelog.txt +++ b/docs/src/info/changelog.txt @@ -1,6 +1,12 @@ SPRING DATA GEMFIRE CHANGELOG ============================= http://www.springsource.org/spring-gemfire +Changes in version 1.3.0.RELEASE (2013-02-15) +General +* Added annotation support for GemFire functions +* Added 'datasource' element to gfe-data namespace to simplify client connection + +See [release notes](https://jira.springsource.org/secure/ReleaseNote.jspa?projectId=10462&version=13300) Changes in version 1.2.1.RELEASE (2012-10-26) --------------------------------------------- diff --git a/docs/src/reference/docbook/introduction/new-features.xml b/docs/src/reference/docbook/introduction/new-features.xml index 064b5dc4..db26aa34 100644 --- a/docs/src/reference/docbook/introduction/new-features.xml +++ b/docs/src/reference/docbook/introduction/new-features.xml @@ -16,7 +16,7 @@ project. -
+
New in the 1.2.1 Release @@ -26,6 +26,20 @@
+
+ New in the 1.3.0.Release + + + Annotation support for GemFire functions. It is now possible to declare and register functions written as POJOs with annotations. + In addition function executions are defined as annotated interfaces, similar to the way Spring Data repositories work. See + + + A datasource tag has been added to the gfe-data XML namespace. This simplifies establishing + a basic client connection to a Cache cluster. + + +
New in the 1.2.0 Release diff --git a/docs/src/reference/docbook/introduction/requirements.xml b/docs/src/reference/docbook/introduction/requirements.xml index 894f640b..9dd08e96 100644 --- a/docs/src/reference/docbook/introduction/requirements.xml +++ b/docs/src/reference/docbook/introduction/requirements.xml @@ -3,6 +3,7 @@ Spring Data GemFire requires JDK level 6.0 and above, Spring Framework 3 and - vFabric GemFire 6.6 and above. + vFabric GemFire 6.6 and above + (version 7 or above is recommended). \ No newline at end of file diff --git a/docs/src/reference/docbook/reference/function-annotations.xml b/docs/src/reference/docbook/reference/function-annotations.xml new file mode 100644 index 00000000..353647b6 --- /dev/null +++ b/docs/src/reference/docbook/reference/function-annotations.xml @@ -0,0 +1,318 @@ + + + + Annotation Support for Function Execution + +
+ Introduction + + Spring Data GemFire 1.3.0 introduces annotation support to simplify + working with GemFire + function execution. The GemFire API provides classes to implement + and register Functions + deployed to cache servers that may be invoked remotely by member + applications, typically cache clients. Functions may execute in parallel, + distributed among multiple servers, combining results in a map-reduce + pattern, or may be targeted to a single server. A Function execution may + be also be targeted to a specific region. + + GemFire's also provides APIs to support remote execution of + functions targeted to various defined scopes (region, member groups, + servers, etc.) and the ability to aggregate results. The API also provides + certain runtime options. The implementation and execution of remote + functions, as with any RPC protocol, requires some boilerplate code. + Spring Data GemFire, true to Spring's core value proposition, aims to hide + the mechanics of remote function execution and allow developers to focus + on POJO programming and business logic. To this end, Spring Data GemFire + introduces annotations to declaratively register public methods as + functions, and the ability to invoke registered functions remotely via + annotated interfaces. +
+ +
+ Implementation vs Execution + + There are two separate concerns to address. First is the function + implementation (server) which must interact with the FunctionContext + to obtain the invocation arguments, the ResultsSender + and other execution context information. The function implementation + typically accesses the Cache and or Region and is typically registered + with the FunctionService + under a unique Id. The application invoking a function (the client) does + not depend on the implementation. To invoke a function remotely, the + application instantiates an Execution + providing the function ID, invocation arguments, the function target or + scope (region, server, servers, member, members). If the function produces + a result, the invoker uses a ResultCollector + to aggregate and acquire the execution results. In certain scenarios, a + custom ResultCollector implementation is required and may be registered + with the Execution. + + + 'Client' and 'Server' are used here in the context of function + execution which may have a different meaning then client and server in a + client-server cache topology. While it is common for a member with a + Client Cache to invoke a function on one or more Cache Server members it + is also possible to execute functions in a peer-to-peer + configuration + +
+ +
+ Implementing a Function + + Using Gemfire APIs, the FunctionContext provides a runtime + invocation context including the client's calling arguments and a + ResultSender interface to send results back to the client. Additionally, + if the function is executed on a Region, the FunctionContext is an + instance of RegionFunctionContext which provides additional context such + as the target Region and any Filter (set of specific keys) associated with + the Execution. If the Region is a Partition Region, the function should + use the PartitonRegionHelper to extract only the local data. + + Using Spring, one can write a simple POJO and enable the Spring + container bind one or more of it's public methods to a Function. The + signature for a POJO method intended to be used as a function must + generally conform to the the client's execution arguments. However, in the + case of a region execution, the region data must also be provided + (presumably the data held in the local partition if the region is a + partition region). Additionally the function may require the filter that + was applied, if any. This suggests that the client and server may share a + contract for the calling arguments but that the method signature may + include additional parameters to pass values provided by the + FunctionContext. One possibility is that the client and server share a + common interface, but this is not required. The only constraint is that + the method signature includes the same sequence of calling arguments with + which the function was invoked after the additional parameters are + resolved. For example, suppose the client provides a String and int as the + calling arguments. These are provided by the FunctionContext as an + array: + + Object[] args = new Object[]{"hello", + 123} + + Then the Spring container should be able to bind to any method + signature similar to the following. Let's ignore the return type for the + moment: + + public Object method1(String s1, int i2) {...} +public Object method2(Map<?,?> data, String s1, int i2) {...} +public Object method3(String s1, Map<?,?>data, int i2) {...} +public Object method4(String s1, Map<?,?> data, Set<?> filter, int i2) {...} +public void method4(String s1, Set<?> filter, int i2, Region<?,?> data) {...} +public void method5(String s1, ResultSender rs, int i2); +public void method6(FunctionContest fc);The general rule is + that once any additional arguments, i.e., region data and filter, are + resolved the remaining arguments must correspond exactly, in order and + type, to the expected calling parameters. The method's return type must be + void or a type that may be serialized (either java.io.Serializable, + DataSerializable, or PDX serializable). The latter is also a requirement + for the calling arguments. The Region data should normally be defined as a + Map, to facilitate unit testing, but may also be of type Region if + necessary. As shown in the example above, it is also valid to pass the + FunctionContext itself, or the ResultSender, if you need to control how + the results are returned to the client. + +
+ Annotations for Function Implementation + + The following example illustrates how annotations are used to + expose a POJO as a GemFire function: + + @Component +public class MyFunctions { + @GemfireFunction + public String function1(String s1, @RegionData Map<?,?> data, int i2) { ... } + + @GemfireFunction("myFunction", HA=true, optimizedForWrite=true, batchSize=100) + public List<String> function2(String s1, @RegionData Map<?,?> data, int i2, @Filter Set<?> keys) { ... } + + @GemfireFunction(hasResult=true) + public void functionWithContext(FunctionContext functionContext) { ... } +} Note that the class itself must be registered as a Spring + bean. Here the @Component annotation is used, but you + may register the bean by any method provided by Spring (e.g. XML + configuration or Java configuration class). This allows the Spring + container to create an instance of this class and wrap it in a PojoFunctionWrapper(PFW). + Spring creates one PFW instance for each method annotated with + @GemfireFunction. Each will all share the same target + object instance to invoke the corresponding method. + + + The fact that the function class is a Spring bean may offer + other benefits since it shares the application context with GemFire + components such as a Cache and Regions. These may be injected into the + class if necessary. + + + Spring creates the wrapper class, and registers the function with + GemFire's Function Service. The function id used to register the + functions must be unique. By convention it defaults to the simple + (unqualified) method name. Note that this annotation also provides + configuration attributes, HA and + optimizedForWrite which correspond to properties + defined by GemFire's Function interface. If the method's return type is + void, then the hasResult property is automatically + set to false; otherwise it is + true. + + For void return types, the annotation provides + a hasResult attribute that can be set to true to + override this convention, as shown in the + functionWithContext method above. Presumably, the + intention is to use the ResultSender directly to send results to the + caller. + + The PFW implements GemFire's Function interface, binds the method + parameters, and invokes the target method in its + execute() method. It also sends the method's return + value using the ResultSender. + +
+ Batching Results + + If the return type is a Collection or Array, then some + consideration must be given to how the results are returned. By + default, the PFW returns the entire collection at once. If the number + of items is large, this may incur a performance penalty. To divide the + payload into small sections (sometimes called chunking), you can set + the batchSize attribute, as illustrated in + function2, above. + If you need more control of the ResultSender, especially if + the method itself would use too much memory to create the + collection, you can pass the ResultSender, or access it via the + FunctionContext, to use it directly within the method. + +
+ +
+ Enabling Annotation Processing + + In accordance with Spring standards, you must explicitly + activate annotation processing for @GemfireFunction using XML: + + <gfe:annotation-driven/> + + or by annotating a Java configuration class: + + @EnableGemfireFunctions +
+
+
+ +
+ Executing a Function + + A process invoking a remote function needs to provide calling + arguments, a function id, the execution target (onRegion, onServers, + onServer, onMember, onMembers) and optionally a Filter set. All you need + to do is define an interface supported by annotations. Spring will create + a dynamic proxy the interface which will use the FunctionService to create + an Execution, invoke the execution and coerce the results to a defined + return type, if necessary. This technique is very similar to the way + Spring Data repositories work, thus some of the configuration and concepts + should be familiar. Generally a single interface definition maps to + multiple function executions, one corresponding to each method defined in + the interface. + +
+ Annotations for Function Execution + + To support client side function execution, the following + annotations are provided: @OnRegion, + @OnServer, @OnServers, + @OnMember, @OnMembers. These + correspond to the Execution implementations GemFire's FunctionService + provides. Each annotation exposes the appropriate attributes. These + annotations also provide an optional resultCollector + attribute whose value is the name of a Spring bean implementing ResultCollector + to use for the execution. + + + The proxy interface binds all declared methods to the same + execution configuration. Although it is expected that single method + interfaces will be common, all methods in the interface are backed by + the same proxy instance and therefore are all share the same + configuration. + + + Here are some examples: + + @OnRegion(region="someRegion", resultCollector="myCollector") +public interface FunctionExecution { + @FunctionId("function1") + public String doIt(String s1, int i2); + public String getString(Object arg1, @Filter Set<Object> keys) ; +} + + By default, the function id is the simple (unqualified) method + name. @FunctionId is used to bind this invocation to + a different function id. + +
+ Enabling Annotation Processing + + The client side uses Spring's component scanning capability to + discover annotated interfaces. To enable function execution annotation + processing, you can use XML: + + <gfe-data:function-executions base-package="org.example.myapp.functions"/> + + Note that the function-executions tag is + provided in the gfe-data namespace. The + base-package attribute is required to avoid + scanning the entiire class path. Additional filters are provided as + described in the Spring reference. + + Or annotate your Java configuration class: + + @EnableGemfireFunctionExecutions(basePackages = "org.example.myapp.functions") +
+
+
+ +
+ Programmatic Function Execution + + Using the annotated interface as described in the previous section, + simply wire your interface into a bean that will invoke the function: + + + @Component + public class MyApp { + + @Autowired FunctionExecution functionExecution; + + public void doSomething() { + functionExecution.doIt("hello", 123); + } + + } + + Alternately, you can use a Function Execution template directly. For + example GemfireOnRegionFunctionTemplate creates an onRegion execution. For + example: + + Set<?,?> myFilter = getFilter(); +Region<?,?> myRegion = getRegion(); +GemfireOnRegionOperations template = new GemfireOnRegionFunctionTemplate(myRegion); +String result = template.executeAndExtract("someFunction",myFilter,"hello","world",1234);Internally, + function executions always return a List. + executeAndExtract assumes a singleton list containing + the result and will attempt to coerce that value into the requested type. + There is also an execute method that returns the List + itself. The first parameter is the function id. The filter argument is + optional. The following arguments are a variable argument list. +
+
diff --git a/src/test/java/org/springframework/data/gemfire/function/config/AnnotationDrivenFunctionsTest.java b/src/test/java/org/springframework/data/gemfire/function/config/AnnotationDrivenFunctionsTest.java index a6b82e0d..fc8d9d21 100644 --- a/src/test/java/org/springframework/data/gemfire/function/config/AnnotationDrivenFunctionsTest.java +++ b/src/test/java/org/springframework/data/gemfire/function/config/AnnotationDrivenFunctionsTest.java @@ -32,6 +32,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.gemstone.gemfire.cache.execute.Function; import com.gemstone.gemfire.cache.execute.FunctionService; + /** * @author David Turanski * @@ -41,30 +42,29 @@ import com.gemstone.gemfire.cache.execute.FunctionService; public class AnnotationDrivenFunctionsTest { @Autowired ApplicationContext applicationContext; - + @Test public void testAnnotatedFunctions() { - + assertTrue(FunctionService.isRegistered("foo")); - + Function function = FunctionService.getFunction("foo"); assertFalse(function.isHA()); assertFalse(function.optimizeForWrite()); assertFalse(function.hasResult()); - + assertTrue(FunctionService.isRegistered("bar")); function = FunctionService.getFunction("bar"); assertTrue(function.isHA()); assertFalse(function.optimizeForWrite()); assertTrue(function.hasResult()); - + assertTrue(FunctionService.isRegistered("foo2")); function = FunctionService.getFunction("foo2"); assertTrue(function.isHA()); assertTrue(function.optimizeForWrite()); assertTrue(function.hasResult()); - - + assertTrue(FunctionService.isRegistered("injectFilter")); function = FunctionService.getFunction("injectFilter"); assertTrue(function.isHA()); @@ -75,26 +75,26 @@ public class AnnotationDrivenFunctionsTest { @Component public static class FooFunction { @GemfireFunction - public void foo () { + public void foo() { } - - @GemfireFunction(HA=true,optimizeForWrite=false) - public String bar () { + + @GemfireFunction(HA = true, optimizeForWrite = false) + public String bar() { return null; } } - + @Component public static class Foo2Function { - @GemfireFunction(id="foo2", HA=true,optimizeForWrite=true) - public List foo (Object someVal, @RegionData Map region, Object someOtherValue) { + @GemfireFunction(id = "foo2", HA = true, optimizeForWrite = true) + public List foo(Object someVal, @RegionData Map region, Object someOtherValue) { return null; } - - @GemfireFunction(id="injectFilter", HA=true,optimizeForWrite=true) - public List injectFilter (@Filter Set keySet) { + + @GemfireFunction(id = "injectFilter", HA = true, optimizeForWrite = true) + public List injectFilter(@Filter Set keySet) { return null; } } - + } diff --git a/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionClientCacheTests.java b/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionClientCacheTests.java index 655760d3..d4675ae8 100644 --- a/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionClientCacheTests.java +++ b/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionClientCacheTests.java @@ -40,36 +40,33 @@ import com.gemstone.gemfire.distributed.DistributedMember; * */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes={TestClientCacheConfig.class}) +@ContextConfiguration(classes = { TestClientCacheConfig.class }) public class FunctionExecutionClientCacheTests { @Autowired ApplicationContext context; - + @Test - public void testContextCreated() throws Exception { - - ClientCache cache = context.getBean("gemfireCache",ClientCache.class); - Pool pool = context.getBean("gemfirePool",Pool.class); + public void testContextCreated() throws Exception { + + ClientCache cache = context.getBean("gemfireCache", ClientCache.class); + Pool pool = context.getBean("gemfirePool", Pool.class); assertEquals("gemfirePool", pool.getName()); assertEquals(1, cache.getDefaultPool().getServers().size()); assertEquals(pool.getServers().get(0), cache.getDefaultPool().getServers().get(0)); - - context.getBean("r1",Region.class); - + + context.getBean("r1", Region.class); + GemfireOnServerFunctionTemplate template = context.getBean(GemfireOnServerFunctionTemplate.class); assertTrue(template.getResultCollector() instanceof MyResultCollector); } - + } - @ImportResource("/org/springframework/data/gemfire/function/config/FunctionExecutionCacheClientTests-context.xml") -@EnableGemfireFunctionExecutions (basePackages = "org.springframework.data.gemfire.function.config.three", - excludeFilters = { - /*@ComponentScan.Filter(type=FilterType.ANNOTATION, value=OnRegion.class), - @ComponentScan.Filter(type=FilterType.ANNOTATION, value=OnServer.class)*/ - } -) +@EnableGemfireFunctionExecutions(basePackages = "org.springframework.data.gemfire.function.config.three", excludeFilters = { +/*@ComponentScan.Filter(type=FilterType.ANNOTATION, value=OnRegion.class), +@ComponentScan.Filter(type=FilterType.ANNOTATION, value=OnServer.class)*/ +}) @Configuration class TestClientCacheConfig { @Bean @@ -87,7 +84,7 @@ class MyResultCollector implements ResultCollector { @Override public void addResult(DistributedMember arg0, Object arg1) { // TODO Auto-generated method stub - + } /* (non-Javadoc) @@ -96,7 +93,7 @@ class MyResultCollector implements ResultCollector { @Override public void clearResults() { // TODO Auto-generated method stub - + } /* (non-Javadoc) @@ -105,7 +102,7 @@ class MyResultCollector implements ResultCollector { @Override public void endResults() { // TODO Auto-generated method stub - + } /* (non-Javadoc) @@ -125,7 +122,5 @@ class MyResultCollector implements ResultCollector { // TODO Auto-generated method stub return null; } - + } - - diff --git a/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionIntegrationTests.java index afda9bf8..d8f25556 100644 --- a/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionIntegrationTests.java @@ -30,40 +30,36 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.gemstone.gemfire.cache.Region; + /** * @author David Turanski * */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes={TestConfig.class}, - initializers=GemfireTestApplicationContextInitializer.class) +@ContextConfiguration(classes = { TestConfig.class }, initializers = GemfireTestApplicationContextInitializer.class) public class FunctionExecutionIntegrationTests { @Autowired ApplicationContext context; - - + @Test public void testProxyFactoryBeanCreated() throws Exception { - OnRegionFunctionProxyFactoryBean factoryBean = (OnRegionFunctionProxyFactoryBean)context.getBean("&testFunction"); - Class serviceInterface = TestUtils.readField("serviceInterface",factoryBean); + OnRegionFunctionProxyFactoryBean factoryBean = (OnRegionFunctionProxyFactoryBean) context + .getBean("&testFunction"); + Class serviceInterface = TestUtils.readField("serviceInterface", factoryBean); assertEquals(serviceInterface, TestOnRegionFunction.class); - - Region r1 = context.getBean("r1",Region.class); - - GemfireOnRegionFunctionTemplate template = TestUtils.readField("gemfireFunctionOperations",factoryBean); - - assertSame(r1, TestUtils.readField("region",template)); - } - -} + Region r1 = context.getBean("r1", Region.class); + + GemfireOnRegionFunctionTemplate template = TestUtils.readField("gemfireFunctionOperations", factoryBean); + + assertSame(r1, TestUtils.readField("region", template)); + } + +} @ImportResource("/org/springframework/data/gemfire/function/config/FunctionExecutionIntegrationTests-context.xml") @EnableGemfireFunctionExecutions(basePackages = "org.springframework.data.gemfire.function.config.two") @Configuration class TestConfig { - + } - - -