SGF-358 - Enhance SDG's Function annotation support to allow strongly-typed arguments in the context of PDX even when read-serialized is set to true.
This commit is contained in:
@@ -3,25 +3,65 @@
|
||||
|
||||
== Introduction
|
||||
|
||||
Spring Data GemFire 1.3.0 introduces annotation support to simplify working with http://gemfire.docs.pivotal.io/latest/userguide/index.html#developing/function_exec/chapter_overview.html[GemFire function execution]. The GemFire API provides classes to implement and register http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/Function.html[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.
|
||||
Spring Data GemFire 1.3.0 introduces annotation support to simplify working with
|
||||
http://gemfire.docs.pivotal.io/latest/userguide/index.html#developing/function_exec/chapter_overview.html[GemFire Function Execution].
|
||||
The GemFire API provides classes to implement and register http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/Function.html[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 at 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.
|
||||
GemFire 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 GemFire 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 http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/FunctionContext.html[FunctionContext] to obtain the invocation arguments, the http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/ResultSender.html[ResultsSender] and other execution context information. The function implementation typically accesses the Cache and or Region and is typically registered with the http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/FunctionService.html[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 http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/Execution.html[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 http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/ResultCollector.html[ResultCollector] to aggregate and acquire the execution results. In certain scenarios, a custom ResultCollector implementation is required and may be registered with the Execution.
|
||||
There are two separate concerns to address. First is the Function implementation (server) which must interact with
|
||||
the http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/FunctionContext.html[FunctionContext]
|
||||
to obtain the invocation arguments, the http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/ResultSender.html[ResultsSender]
|
||||
and other execution context information. The Function implementation typically accesses the Cache and or Region
|
||||
and is typically registered with the http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/FunctionService.html[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 http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/Execution.html[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 http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/ResultCollector.html[ResultCollector]
|
||||
to aggregate and acquire the execution results. In certain scenarios, a custom ResultCollector implementation
|
||||
is required and may be registered with the Execution.
|
||||
|
||||
NOTE: '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
|
||||
NOTE: 'Client' and 'Server' are used here in the context of Function execution which may have a different meaning
|
||||
than 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
|
||||
(P2P) 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 PartitionRegionHelper to extract only the local data.
|
||||
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 PartitionRegionHelper 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:
|
||||
Using Spring, a developer can write a simple POJO and enable the Spring container to 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:
|
||||
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:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -34,40 +74,68 @@ 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.
|
||||
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:
|
||||
The following example illustrates how annotations are used to expose a POJO as a GemFire Function:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Component
|
||||
public class MyFunctions {
|
||||
public class ApplicationFunctions {
|
||||
|
||||
@GemfireFunction
|
||||
public String function1(String s1, @RegionData Map<?,?> data, int i2) { ... }
|
||||
public String function1(String value, @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) { ... }
|
||||
public List<String> function2(String value, @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 https://github.com/spring-projects/spring-data-gemfire/blob/master/src/main/java/org/springframework/data/gemfire/function/PojoFunctionWrapper.java[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.
|
||||
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
|
||||
https://github.com/spring-projects/spring-data-gemfire/blob/master/src/main/java/org/springframework/data/gemfire/function/PojoFunctionWrapper.java[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.
|
||||
|
||||
NOTE: 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.
|
||||
NOTE: The fact that the Function class is a Spring bean may offer other benefits since it shares the ApplicationContext
|
||||
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`.
|
||||
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 set to `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.
|
||||
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.
|
||||
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. NOTE: 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.
|
||||
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.
|
||||
|
||||
NOTE: 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
|
||||
|
||||
@@ -88,13 +156,26 @@ or by annotating a Java configuration class:
|
||||
[[function-execution]]
|
||||
== 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.
|
||||
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 a developer need do is
|
||||
define an interface supported by annotations. Spring will create a dynamic proxy for 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 http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/ResultCollector.html[ResultCollector] to use for the 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
|
||||
http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/cache/execute/ResultCollector.html[ResultCollector]
|
||||
to use for the execution.
|
||||
|
||||
NOTE: 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.
|
||||
NOTE: 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 all share the same configuration.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
@@ -102,26 +183,33 @@ 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) ;
|
||||
String doIt(String s1, int i2);
|
||||
|
||||
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.
|
||||
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:
|
||||
The client-side uses Spring's component scanning capability to discover annotated interfaces. To enable
|
||||
Function execution annotation processing, you can use XML:
|
||||
|
||||
[source,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 http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-scanning-filters[reference].
|
||||
Note that the `function-executions` element is provided in the `gfe-data` namespace. The `base-package` attribute
|
||||
is required to avoid scanning the entire classpath. Additional filters are provided as described in the Spring
|
||||
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-scanning-filters[reference].
|
||||
|
||||
Or annotate your Java configuration class:
|
||||
Optionally, a developer can annotate her Java configuration class:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -130,7 +218,8 @@ Or annotate your Java configuration class:
|
||||
|
||||
== 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:
|
||||
Using the annotated interface as described in the previous section, simply wire your interface into a bean
|
||||
that will invoke the Function:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -146,7 +235,8 @@ Using the annotated interface as described in the previous section, simply wire
|
||||
}
|
||||
----
|
||||
|
||||
Alternately, you can use a Function Execution template directly. For example GemfireOnRegionFunctionTemplate creates an onRegion execution. For example:
|
||||
Alternately, you can use a Function Execution template directly. For example `GemfireOnRegionFunctionTemplate` creates
|
||||
an `onRegion` Function execution. For example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -156,5 +246,149 @@ GemfireOnRegionOperations template = new GemfireOnRegionFunctionTemplate(myRegio
|
||||
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.
|
||||
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.
|
||||
|
||||
== Function Execution with PDX
|
||||
|
||||
When using Spring Data GemFire's Function annotation support combined with GemFire's http://gemfire.docs.pivotal.io/latest/userguide/index.html#developing/data_serialization/gemfire_pdx_serialization.html[PDX serialization],
|
||||
there are a few logistical things to keep in mind.
|
||||
|
||||
As explained above, and by way of example, typically developers will define GemFire Functions using POJO classes
|
||||
annotated with Spring Data GemFire http://docs.spring.io/spring-data-gemfire/docs/1.6.0.M1/api/org/springframework/data/gemfire/function/annotation/package-frame.html[Function annotations]
|
||||
as so...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class OrderFunctions {
|
||||
|
||||
@GemfireFunction(...)
|
||||
Order process(@RegionData data, Order order, OrderSource orderSourceEnum, Integer count);
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: the Integer count parameter is an arbitrary argument as is the separation of the Order and OrderSource Enum,
|
||||
which might be logical to combine. However, the arguments were setup this way to demonstrate the problem with
|
||||
Function executions in the context of PDX.
|
||||
|
||||
Your Order and OrderSource enum might be as follows...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class Order ... {
|
||||
|
||||
private Long orderNumber;
|
||||
private Calendar orderDateTime;
|
||||
private Customer customer;
|
||||
private List<Item> items
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
public enum OrderSource {
|
||||
ONLINE,
|
||||
PHONE,
|
||||
POINT_OF_SALE
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Of course, a developer may define a Function Execution interface to call the 'process' GemFire Server Function...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@OnServer
|
||||
public interface OrderProcessingFunctions {
|
||||
Order process(Order order, OrderSource orderSourceEnum, Integer count);
|
||||
}
|
||||
----
|
||||
|
||||
Clearly, this `process(..)` Order Function is being called from a client-side, client Cache (`<gfe:client-cache/>`)
|
||||
member-based application. This means that the Function arguments must be serializable. The same is true when
|
||||
invoking peer-to-peer member Functions (`@OnMember(s)) between peers in the cluster. Any form of `distribution`
|
||||
requires the data transmitted between client and server, or peers to be serializable.
|
||||
|
||||
Now, if the developer has configured GemFire to use PDX for serialization (instead of Java serialization, for instance)
|
||||
it is common for developers to set the `read-serialized` attribute to *true* on the GemFire server(s)...
|
||||
|
||||
`<gfe:cache ... pdx-read-serialized="true"/>`
|
||||
|
||||
This causes all values read from the Cache (i.e. Regions) as well as information passed between client and servers,
|
||||
or peers to remain in serialized form, include, but not limited to Function arguments.
|
||||
|
||||
GemFire will only serialize application domain object types that you have specifically configured (registered),
|
||||
either using GemFire's http://gemfire.docs.pivotal.io/latest/userguide/index.html#developing/data_serialization/auto_serialization.html[ReflectionBasedAutoSerializer],
|
||||
or specifically (and recommended) using a "custom" GemFire http://gemfire.docs.pivotal.io/latest/userguide/index.html#developing/data_serialization/use_pdx_serializer.html[PdxSerializer]
|
||||
for your application domain types.
|
||||
|
||||
What is less than apparent, is that GemFire automatically handles Java Enum types regardless of whether they are
|
||||
explicitly configured (registered with a `ReflectionBasedAutoSerializer` regex pattern to the `classes` parameter,
|
||||
or handled by a "custom" GemFire `PdxSerializer`) or not, and despite the fact that Java Enums implement
|
||||
`java.io.Serializable`.
|
||||
|
||||
So, when a developer has `pdx-read-serialized` set to *true* on the GemFire Servers on which the GemFire Functions
|
||||
(including Spring Data GemFire registered, Function annotated POJO classes), then the developer may encounter surprising
|
||||
behavior when invoking the Function Execution.
|
||||
|
||||
What the developer may pass as arguments when invoking the Function is...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
orderProcessingFunctions.process(new Order(123, customer, Calendar.getInstance(), items), OrderSource.ONLINE, 400);
|
||||
----
|
||||
|
||||
But, in actuality, what GemFire executes the Function on the Server is...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
process(regionData, order:PdxInstance, :PdxInstanceEnum, 400);
|
||||
----
|
||||
|
||||
Notice that the `Order` and `OrderSource` have passed to the Function as http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/pdx/PdxInstance.html[PDX instances].
|
||||
Again, this is all because `read-serialized` is set to true on the GemFire Server, which may be necessary in cases
|
||||
where the GemFire Servers are interacting with multiple different client types (e.g. native clients).
|
||||
|
||||
This flies in the face of Spring Data GemFire's, "strongly-typed", Function annotated POJO class method signatures,
|
||||
as the developer is expecting application domain object types (not PDX serialized objects).
|
||||
|
||||
So, as of Spring Data GemFire (SDG) *1.6*, SDG introduces enhanced Function support to automatically convert method
|
||||
arguments that are of type PDX to the desired application domain object types when the developer of the Function
|
||||
expects his Function arguments to be "strongly-typed".
|
||||
|
||||
However, this also requires the developer to explicitly register a GemFire `PdxSerializer` on the GemFire Servers
|
||||
where the SDG annotated POJO Function is registered and used, e.g. ...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
<bean id="customPdxSerializer" class="x.y.z.serialization.pdx.MyCustomPdxSerializer"/>
|
||||
|
||||
<gfe:cache ... pdx-serializer-ref="customPdxSerializeer" pdx-read-serialized="true"/>
|
||||
----
|
||||
|
||||
Alternatively, a developer my use GemFire's http://gemfire.docs.pivotal.io/latest/javadocs/japi/com/gemstone/gemfire/pdx/ReflectionBasedAutoSerializer.html[ReflectionBasedAutoSerializer].
|
||||
Of course, it is recommend to use a "custom" `PdxSerializer` where possible given the performance implications of using
|
||||
Java's Reflection functionality.
|
||||
|
||||
Finally, Spring Data GemFire is careful not to convert your Function arguments if you really want to treat your
|
||||
Function arguments generically, or as one of GemFire's PDX types...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@GemfireFunction
|
||||
public Object genericFunction(String value, Object domainObject, PdxInstanceEnum enum) {
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Spring Data GemFire will only convert PDX type data to corresponding application domain object types
|
||||
if and only if the corresponding application domain object types are on the classpath the the Function annotated
|
||||
POJO method expects it.
|
||||
|
||||
For a good example of "custom", "composed" application-specific GemFire `PdxSerializers` as well as appropriate
|
||||
POJO Function parameter type handling based on the method signature, see Spring Data GemFire's
|
||||
https://github.com/spring-projects/spring-data-gemfire/blob/master/src/test/java/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest.java[ClientCacheFunctionExecutionWithPdxIntegrationTest] class.
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
*/
|
||||
package org.springframework.data.gemfire.function;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.gemstone.gemfire.cache.execute.FunctionContext;
|
||||
|
||||
/**
|
||||
@@ -23,7 +25,19 @@ class DefaultFunctionArgumentResolver implements FunctionArgumentResolver {
|
||||
|
||||
private static final Object[] EMPTY_ARRAY = new Object[0];
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.reflect.Method
|
||||
*/
|
||||
@Override
|
||||
public Method getFunctionAnnotatedMethod() {
|
||||
throw new UnsupportedOperationException("Not Implemented!");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.data.gemfire.function.FunctionArgumentResolver#resolveFunctionArguments(com.gemstone.gemfire.cache.execute.FunctionContext)
|
||||
*/
|
||||
@Override
|
||||
|
||||
@@ -12,14 +12,23 @@
|
||||
*/
|
||||
package org.springframework.data.gemfire.function;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.gemstone.gemfire.cache.execute.FunctionContext;
|
||||
|
||||
/**
|
||||
* Strategy Interface for resolving function invocation arguments, given a {@link FunctionContext}
|
||||
* @author David Turanski
|
||||
* @since 1.3.0
|
||||
* The FunctionArgumentResolver interface is a Strategy Interface for resolving Function invocation arguments,
|
||||
* given a {@link FunctionContext}.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author John Blum
|
||||
* @see com.gemstone.gemfire.cache.execute.FunctionContext
|
||||
* @since 1.3.0
|
||||
*/
|
||||
interface FunctionArgumentResolver {
|
||||
|
||||
Method getFunctionAnnotatedMethod();
|
||||
|
||||
Object[] resolveFunctionArguments(FunctionContext functionContext);
|
||||
|
||||
}
|
||||
|
||||
@@ -34,68 +34,68 @@ import com.gemstone.gemfire.cache.partition.PartitionRegionHelper;
|
||||
* @since 1.3.0
|
||||
*
|
||||
*/
|
||||
class FunctionContextInjectingArgumentResolver extends DefaultFunctionArgumentResolver {
|
||||
class FunctionContextInjectingArgumentResolver extends PdxFunctionArgumentResolver {
|
||||
|
||||
private static Log logger = LogFactory.getLog(FunctionContextInjectingArgumentResolver.class);
|
||||
private static final Log logger = LogFactory.getLog(FunctionContextInjectingArgumentResolver.class);
|
||||
|
||||
private final int regionParameterPosition;
|
||||
private final int filterParameterPosition;
|
||||
private final int functionContextParameterPosition;
|
||||
private final int regionParameterPosition;
|
||||
private final int resultSenderParameterPosition;
|
||||
|
||||
private final Method method;
|
||||
|
||||
public FunctionContextInjectingArgumentResolver(Method method) {
|
||||
|
||||
this.method = method;
|
||||
int annotatedRegionDataParameterPosition = GemfireFunctionUtils.getAnnotationParameterPosition(method,
|
||||
RegionData.class, new Class[] { Map.class });
|
||||
|
||||
int regionDataAnnotationParameterPosition = GemfireFunctionUtils.getAnnotationParameterPosition(
|
||||
method, RegionData.class, new Class[] { Map.class });
|
||||
|
||||
int regionTypeParameterPosition = getArgumentTypePosition(method, Region.class);
|
||||
|
||||
if (annotatedRegionDataParameterPosition >= 0 && regionTypeParameterPosition >= 0) {
|
||||
Assert.isTrue(
|
||||
annotatedRegionDataParameterPosition == regionTypeParameterPosition,
|
||||
String.format(
|
||||
"Function method signature for method %s cannot contain an @RegionData parameter and a different Region type parameter",
|
||||
method.getName()));
|
||||
if (regionDataAnnotationParameterPosition >= 0 && regionTypeParameterPosition >= 0) {
|
||||
Assert.isTrue(regionDataAnnotationParameterPosition == regionTypeParameterPosition, String.format(
|
||||
"Function method signature for method %s cannot contain an @RegionData parameter and a different Region type parameter",
|
||||
method.getName()));
|
||||
}
|
||||
|
||||
int tempRegionParameterPosition = -1;
|
||||
regionParameterPosition = (regionDataAnnotationParameterPosition >= 0 ? regionDataAnnotationParameterPosition
|
||||
: (regionTypeParameterPosition >= 0 ? regionTypeParameterPosition : -1));
|
||||
|
||||
if (annotatedRegionDataParameterPosition >= 0) {
|
||||
tempRegionParameterPosition = annotatedRegionDataParameterPosition;
|
||||
} else if (regionTypeParameterPosition >= 0) {
|
||||
tempRegionParameterPosition = regionTypeParameterPosition;
|
||||
}
|
||||
|
||||
regionParameterPosition = tempRegionParameterPosition;
|
||||
filterParameterPosition = GemfireFunctionUtils.getAnnotationParameterPosition(method, Filter.class,
|
||||
new Class[] { Set.class });
|
||||
functionContextParameterPosition = getArgumentTypePosition(method, FunctionContext.class);
|
||||
resultSenderParameterPosition = getArgumentTypePosition(method, ResultSender.class);
|
||||
new Class[] { Set.class });
|
||||
|
||||
if (regionParameterPosition >= 0 && filterParameterPosition >= 0) {
|
||||
Assert.state(regionParameterPosition != filterParameterPosition,
|
||||
"region parameter and filter parameter must be different");
|
||||
"region parameter and filter parameter must be different");
|
||||
}
|
||||
|
||||
functionContextParameterPosition = getArgumentTypePosition(method, FunctionContext.class);
|
||||
|
||||
resultSenderParameterPosition = getArgumentTypePosition(method, ResultSender.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method getFunctionAnnotatedMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] resolveFunctionArguments(FunctionContext functionContext) {
|
||||
|
||||
Object[] args = super.resolveFunctionArguments(functionContext);
|
||||
|
||||
if (functionContext instanceof RegionFunctionContext) {
|
||||
if (this.regionParameterPosition >= 0) {
|
||||
args = ArrayUtils.insert(args, regionParameterPosition,
|
||||
getRegionForContext((RegionFunctionContext) functionContext));
|
||||
args = ArrayUtils.insert(args, regionParameterPosition, getRegionForContext(
|
||||
(RegionFunctionContext) functionContext));
|
||||
}
|
||||
|
||||
if (this.filterParameterPosition >= 0) {
|
||||
args = ArrayUtils.insert(args, filterParameterPosition,
|
||||
((RegionFunctionContext) functionContext).getFilter());
|
||||
((RegionFunctionContext) functionContext).getFilter());
|
||||
}
|
||||
}
|
||||
|
||||
if (this.functionContextParameterPosition >= 0) {
|
||||
args = ArrayUtils.insert(args, functionContextParameterPosition, functionContext);
|
||||
}
|
||||
@@ -105,47 +105,53 @@ class FunctionContextInjectingArgumentResolver extends DefaultFunctionArgumentRe
|
||||
}
|
||||
|
||||
Assert.isTrue(args.length == method.getParameterTypes().length, String.format(
|
||||
"wrong number of arguments for method %s. Expected :%d, actual: %d", method.getName(),
|
||||
"wrong number of arguments for method %s. Expected %d, but was %d", method.getName(),
|
||||
method.getParameterTypes().length, args.length));
|
||||
|
||||
return args;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* @param regionFunctionContext
|
||||
* @return
|
||||
* (non-Javadoc)
|
||||
* @see com.gemstone.gemfire.cache.execute.RegionFunctionContext
|
||||
*/
|
||||
private static Region<?, ?> getRegionForContext(RegionFunctionContext regionFunctionContext) {
|
||||
|
||||
Region<?, ?> region = regionFunctionContext.getDataSet();
|
||||
|
||||
if (PartitionRegionHelper.isPartitionedRegion(region)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("this is a partitioned region - filtering local data for context");
|
||||
}
|
||||
region = PartitionRegionHelper.getLocalDataForContext(regionFunctionContext);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("region contains " + region.size() + " items");
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*/
|
||||
private static int getArgumentTypePosition(Method method, Class<?> requiredType) {
|
||||
int index = 0;
|
||||
int position = -1;
|
||||
int i = 0;
|
||||
for (Class<?> clazz : method.getParameterTypes()) {
|
||||
if (requiredType.equals(clazz)) {
|
||||
Assert.state(
|
||||
position < 0,
|
||||
String.format("Method %s signature cannot contain more than one parameter of type %s.",
|
||||
method.getName(), requiredType.getName()));
|
||||
position = i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return position;
|
||||
|
||||
for (Class<?> parameterType : method.getParameterTypes()) {
|
||||
if (requiredType.equals(parameterType)) {
|
||||
Assert.state(position < 0, String.format(
|
||||
"Method %s signature cannot contain more than one parameter of type %s.",
|
||||
method.getName(), requiredType.getName()));
|
||||
|
||||
position = index;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* 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;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
@@ -26,23 +27,23 @@ import org.springframework.util.StringUtils;
|
||||
import com.gemstone.gemfire.cache.execute.FunctionService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author David Turanski
|
||||
*
|
||||
*/
|
||||
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}
|
||||
*
|
||||
* Wrap a target object and method in a GemFire Function and register the function to the {@link FunctionService}
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public static void registerFunctionForPojoMethod(Object target, Method method, Map<String, Object> attributes,
|
||||
boolean overwrite) {
|
||||
boolean overwrite) {
|
||||
|
||||
String id = attributes.containsKey("id") ? (String) attributes.get("id") : "";
|
||||
|
||||
PojoFunctionWrapper function = new PojoFunctionWrapper(target, method, id);
|
||||
@@ -57,18 +58,15 @@ public abstract class GemfireFunctionUtils {
|
||||
|
||||
if (attributes.containsKey("batchSize")) {
|
||||
int batchSize = (Integer) attributes.get("batchSize");
|
||||
Assert.isTrue(
|
||||
batchSize >= 0,
|
||||
String.format("batchSize must be a non-negative value %s.%s", target.getClass().getName(),
|
||||
method.getName()));
|
||||
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")) {
|
||||
boolean hasResult = (Boolean) attributes.get("hasResult");
|
||||
//Only set if true
|
||||
if (hasResult) {
|
||||
function.setHasResult(hasResult);
|
||||
// only set if true TODO figure out why???
|
||||
if (Boolean.TRUE.equals(attributes.get("hasResult"))) {
|
||||
function.setHasResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +84,8 @@ public abstract class GemfireFunctionUtils {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("registered function " + function.getId());
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("function " + function.getId() + "is already registered");
|
||||
}
|
||||
@@ -95,7 +94,7 @@ public abstract class GemfireFunctionUtils {
|
||||
|
||||
/**
|
||||
* Determine the order position of a an annotated method parameter
|
||||
*
|
||||
*
|
||||
* @param method the {@link Method} instance
|
||||
* @param targetAnnotationType the annotation
|
||||
* @param requiredTypes an array of valid parameter types for the annotation
|
||||
@@ -103,42 +102,48 @@ public abstract class GemfireFunctionUtils {
|
||||
*/
|
||||
public static int getAnnotationParameterPosition(Method method, Class<?> targetAnnotationType,
|
||||
Class<?>[] requiredTypes) {
|
||||
|
||||
int position = -1;
|
||||
|
||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
|
||||
if (parameterAnnotations.length > 0) {
|
||||
int position = -1;
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
List<Class<?>> requiredTypesList = Arrays.asList(requiredTypes);
|
||||
|
||||
for (int i = 0; i < parameterAnnotations.length; i++) {
|
||||
Annotation[] annotations = parameterAnnotations[i];
|
||||
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()));
|
||||
Assert.state(position < 0, String.format(
|
||||
"Method %s signature cannot contain more than one parameter annotated with type %s",
|
||||
method.getName(), targetAnnotationType.getName()));
|
||||
|
||||
boolean isRequiredType = false;
|
||||
for (Class<?> requiredType: requiredTypesList) {
|
||||
if (requiredType.isAssignableFrom(paramTypes[i])) {
|
||||
|
||||
for (Class<?> requiredType : requiredTypesList) {
|
||||
if (requiredType.isAssignableFrom(parameterTypes[index])) {
|
||||
isRequiredType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.isTrue(isRequiredType, String.format(
|
||||
"Parameter of type %s annotated with %s must be assignable from one of type %s in method %s",
|
||||
paramTypes[i], targetAnnotationType.getName(),
|
||||
StringUtils.arrayToCommaDelimitedString(requiredTypes), method.getName()));
|
||||
position = i;
|
||||
}
|
||||
|
||||
Assert.isTrue(isRequiredType, String.format(
|
||||
"Parameter of type %s annotated with %s must be assignable from one of type %s in method %s",
|
||||
parameterTypes[index], targetAnnotationType.getName(),
|
||||
StringUtils.arrayToCommaDelimitedString(requiredTypes), method.getName()));
|
||||
|
||||
position = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
return -1;
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2010-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;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.CacheClosedException;
|
||||
import com.gemstone.gemfire.cache.CacheFactory;
|
||||
import com.gemstone.gemfire.cache.execute.FunctionContext;
|
||||
import com.gemstone.gemfire.pdx.PdxInstance;
|
||||
|
||||
/**
|
||||
* The PdxFunctionArgumentResolver class is a Spring Data GemFire FunctionArgumentResolver that automatically resolves
|
||||
* PDX types when GemFire is configured with read-serialized set to true, but the application domain classes
|
||||
* are actually on the classpath.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.gemfire.function.DefaultFunctionArgumentResolver
|
||||
* @see com.gemstone.gemfire.pdx.PdxInstance
|
||||
* @since 1.5.2
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
class PdxFunctionArgumentResolver extends DefaultFunctionArgumentResolver {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see com.gemstone.gemfire.cache.execute.FunctionContext
|
||||
*/
|
||||
@Override
|
||||
public Object[] resolveFunctionArguments(final FunctionContext functionContext) {
|
||||
Object[] functionArguments = super.resolveFunctionArguments(functionContext);
|
||||
|
||||
if (isPdxSerializerConfigured()) {
|
||||
int index = 0;
|
||||
|
||||
for (Object functionArgument : functionArguments) {
|
||||
if (functionArgument instanceof PdxInstance) {
|
||||
String className = ((PdxInstance) functionArgument).getClassName();
|
||||
|
||||
if (isDeserializationNecessary(className)) {
|
||||
functionArguments[index] = ((PdxInstance) functionArgument).getObject();
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return functionArguments;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.reflect.Method
|
||||
*/
|
||||
@Override
|
||||
public Method getFunctionAnnotatedMethod() {
|
||||
throw new UnsupportedOperationException("Not Implemented!");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see com.gemstone.gemfire.cache.Cache#getPdxSerializer()
|
||||
* @see com.gemstone.gemfire.cache.CacheFactory#getAnyInstance()
|
||||
*/
|
||||
boolean isPdxSerializerConfigured() {
|
||||
try {
|
||||
return (CacheFactory.getAnyInstance().getPdxSerializer() != null);
|
||||
}
|
||||
catch (CacheClosedException ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadac)
|
||||
*
|
||||
* @see #isOnClasspath(String)
|
||||
* @see #functionAnnotatedMethodHasParameterOfType(String)
|
||||
*/
|
||||
boolean isDeserializationNecessary(final String className) {
|
||||
return (isOnClasspath(className) && functionAnnotatedMethodHasParameterOfType(className));
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Thread#currentThread()
|
||||
* @see java.lang.Thread#getContextClassLoader()
|
||||
* @see org.springframework.util.ClassUtils#isPresent(String, ClassLoader)
|
||||
*/
|
||||
boolean isOnClasspath(final String className) {
|
||||
return ClassUtils.isPresent(className, Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see #getFunctionAnnotatedMethod()
|
||||
* @see java.lang.reflect.Method#getParameterTypes()
|
||||
*/
|
||||
boolean functionAnnotatedMethodHasParameterOfType(final String className) {
|
||||
for (Class<?> parameterType : getFunctionAnnotatedMethod().getParameterTypes()) {
|
||||
if (parameterType.getName().equals(className)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,74 +41,70 @@ public class PojoFunctionWrapper implements Function {
|
||||
private static transient Log logger = LogFactory.getLog(PojoFunctionWrapper.class);
|
||||
|
||||
private volatile boolean HA;
|
||||
private volatile boolean optimizeForWrite;
|
||||
private volatile boolean hasResult;
|
||||
private final Object target;
|
||||
private final Method method;
|
||||
private final String id;
|
||||
private volatile boolean optimizeForWrite;
|
||||
|
||||
private volatile int batchSize;
|
||||
|
||||
private final FunctionArgumentResolver functionArgumentResolver;
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final Object target;
|
||||
|
||||
private final String id;
|
||||
|
||||
public PojoFunctionWrapper(Object target, Method method, String id) {
|
||||
|
||||
this.functionArgumentResolver = new FunctionContextInjectingArgumentResolver(method);
|
||||
|
||||
this.id = StringUtils.hasText(id) ? id : method.getName();
|
||||
this.target = target;
|
||||
this.method = method;
|
||||
|
||||
this.id = (StringUtils.hasText(id) ? id : method.getName());
|
||||
this.HA = false;
|
||||
|
||||
this.hasResult = !(method.getReturnType().equals(void.class));
|
||||
|
||||
this.optimizeForWrite = false;
|
||||
}
|
||||
|
||||
//@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
//@Override
|
||||
public boolean hasResult() {
|
||||
return this.hasResult;
|
||||
}
|
||||
|
||||
//@Override
|
||||
public boolean isHA() {
|
||||
return this.HA;
|
||||
}
|
||||
|
||||
public void setHA(boolean HA) {
|
||||
this.HA = HA;
|
||||
}
|
||||
|
||||
//@Override
|
||||
public boolean optimizeForWrite() {
|
||||
return this.optimizeForWrite;
|
||||
}
|
||||
|
||||
public void setOptimizeForWrite(boolean optimizeForWrite) {
|
||||
this.optimizeForWrite = optimizeForWrite;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public void setHA(boolean HA) {
|
||||
this.HA = HA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHA() {
|
||||
return this.HA;
|
||||
}
|
||||
|
||||
public void setHasResult(boolean hasResult) {
|
||||
this.hasResult = hasResult;
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void execute(FunctionContext functionContext) {
|
||||
@Override
|
||||
public boolean hasResult() {
|
||||
return this.hasResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setOptimizeForWrite(boolean optimizeForWrite) {
|
||||
this.optimizeForWrite = optimizeForWrite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean optimizeForWrite() {
|
||||
return this.optimizeForWrite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final FunctionContext functionContext) {
|
||||
Object[] args = this.functionArgumentResolver.resolveFunctionArguments(functionContext);
|
||||
|
||||
Object result = null;
|
||||
|
||||
result = invokeTargetMethod(args);
|
||||
Object result = invokeTargetMethod(args);
|
||||
|
||||
if (hasResult()) {
|
||||
sendResults(functionContext.getResultSender(), result);
|
||||
@@ -116,32 +112,33 @@ 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", method.getName(),
|
||||
target.getClass().getName(), this.id));
|
||||
|
||||
for (Object arg : args) {
|
||||
logger.debug("arg:" + arg.getClass().getName() + " " + arg.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return (Object) ReflectionUtils.invokeMethod(method, target, (Object[]) args);
|
||||
return ReflectionUtils.invokeMethod(method, target, (Object[]) args);
|
||||
}
|
||||
|
||||
private void sendResults(ResultSender<Object> resultSender, Object result) {
|
||||
if (result == null) {
|
||||
resultSender.lastResult(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ObjectUtils.isArray(result)) {
|
||||
new BatchingResultSender(batchSize, resultSender).sendArrayResults(result);
|
||||
} else if (Iterable.class.isAssignableFrom(result.getClass())) {
|
||||
new BatchingResultSender(batchSize, resultSender).sendResults((Iterable<?>) result);
|
||||
} else {
|
||||
resultSender.lastResult(result);
|
||||
else {
|
||||
if (ObjectUtils.isArray(result)) {
|
||||
new BatchingResultSender(batchSize, resultSender).sendArrayResults(result);
|
||||
}
|
||||
else if (Iterable.class.isAssignableFrom(result.getClass())) {
|
||||
new BatchingResultSender(batchSize, resultSender).sendResults((Iterable<?>) result);
|
||||
}
|
||||
else {
|
||||
resultSender.lastResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* Copyright 2010-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;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.gemfire.ForkUtil;
|
||||
import org.springframework.data.gemfire.fork.SpringCacheServerProcess;
|
||||
import org.springframework.data.gemfire.function.annotation.GemfireFunction;
|
||||
import org.springframework.data.gemfire.function.sample.ApplicationDomainFunctionExecutions;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.pdx.PdxInstance;
|
||||
import com.gemstone.gemfire.pdx.PdxInstanceFactory;
|
||||
import com.gemstone.gemfire.pdx.PdxReader;
|
||||
import com.gemstone.gemfire.pdx.PdxSerializer;
|
||||
import com.gemstone.gemfire.pdx.PdxWriter;
|
||||
import com.gemstone.gemfire.pdx.internal.PdxInstanceEnum;
|
||||
|
||||
/**
|
||||
* The ClientCacheFunctionExecutionWithPdxIntegrationTest class is a test suite of test cases testing Spring Data
|
||||
* GemFire's Function annotation support and interaction between a GemFire client and server Cache
|
||||
* when PDX is configured and read-serialized is set to true.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.junit.runner.RunWith
|
||||
* @see org.springframework.data.gemfire.fork.SpringCacheServerProcess
|
||||
* @see org.springframework.data.gemfire.function.annotation.GemfireFunction
|
||||
* @see org.springframework.data.gemfire.function.sample.ApplicationDomainFunctionExecutions
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @see com.gemstone.gemfire.cache.client.ClientCache
|
||||
* @see com.gemstone.gemfire.pdx.PdxInstance
|
||||
* @see com.gemstone.gemfire.pdx.PdxSerializer
|
||||
* @see com.gemstone.gemfire.pdx.internal.PdxInstanceEnum
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
public class ClientCacheFunctionExecutionWithPdxIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private ClientCache gemfireClientCache;
|
||||
|
||||
@Autowired
|
||||
private ApplicationDomainFunctionExecutions functionExecutions;
|
||||
|
||||
@BeforeClass
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setupSpringGemFireServer() throws IOException {
|
||||
ForkUtil.startCacheServer(SpringCacheServerProcess.class.getName() + " "
|
||||
+ toPathname(ClientCacheFunctionExecutionWithPdxIntegrationTest.class).concat("-server-context.xml"));
|
||||
}
|
||||
|
||||
protected static String toPathname(final Class type) {
|
||||
return File.separator.concat(type.getName().replaceAll("\\.", File.separator));
|
||||
}
|
||||
|
||||
protected PdxInstance toPdxInstance(final Map<String, Object> pdxData) {
|
||||
PdxInstanceFactory pdxInstanceFactory = gemfireClientCache.createPdxInstanceFactory(pdxData.get("@type").toString());
|
||||
|
||||
for (Map.Entry<String, Object> entry : pdxData.entrySet()) {
|
||||
pdxInstanceFactory.writeObject(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return pdxInstanceFactory.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertedFunctionArgumentTypes() {
|
||||
Class[] argumentTypes = functionExecutions.captureConvertedArgumentTypes("test", 1, Boolean.TRUE,
|
||||
new Person("Jon", "Doe"), Gender.MALE);
|
||||
|
||||
assertNotNull(argumentTypes);
|
||||
assertEquals(5, argumentTypes.length);
|
||||
assertEquals(String.class, argumentTypes[0]);
|
||||
assertEquals(Integer.class, argumentTypes[1]);
|
||||
assertEquals(Boolean.class, argumentTypes[2]);
|
||||
assertEquals(Person.class, argumentTypes[3]);
|
||||
assertEquals(Gender.class, argumentTypes[4]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnconvertedFunctionArgumentTypes() {
|
||||
Class[] argumentTypes = functionExecutions.captureUnconvertedArgumentTypes("test", 2, Boolean.FALSE,
|
||||
new Person("Jane", "Doe"), Gender.FEMALE);
|
||||
|
||||
assertNotNull(argumentTypes);
|
||||
assertEquals(5, argumentTypes.length);
|
||||
assertEquals(String.class, argumentTypes[0]);
|
||||
assertEquals(Integer.class, argumentTypes[1]);
|
||||
assertEquals(Boolean.class, argumentTypes[2]);
|
||||
assertTrue(PdxInstance.class.isAssignableFrom(argumentTypes[3]));
|
||||
assertEquals(PdxInstanceEnum.class, argumentTypes[4]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAddressFieldValue() {
|
||||
assertEquals("Portland", functionExecutions.getAddressField(new Address(
|
||||
"100 Main St.", "Portland", "OR", "97205"), "city"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPdxDataFieldValue() {
|
||||
Map<String, Object> pdxData = new HashMap<String, Object>(3);
|
||||
|
||||
pdxData.put("@type", "x.y.z.domain.MyApplicationDomainType");
|
||||
pdxData.put("booleanField", Boolean.TRUE);
|
||||
pdxData.put("integerField", 123);
|
||||
pdxData.put("stringField", "test");
|
||||
|
||||
Integer value = (Integer) functionExecutions.getDataField(toPdxInstance(pdxData), "integerField");
|
||||
|
||||
assertEquals(pdxData.get("integerField"), value);
|
||||
}
|
||||
|
||||
public static class ApplicationDomainFunctions {
|
||||
|
||||
private Class[] getArgumentTypes(final Object... arguments) {
|
||||
Class[] argumentTypes = new Class[arguments.length];
|
||||
int index = 0;
|
||||
|
||||
for (Object argument : arguments) {
|
||||
argumentTypes[index] = arguments[index].getClass();
|
||||
index++;
|
||||
}
|
||||
|
||||
return argumentTypes;
|
||||
}
|
||||
|
||||
@GemfireFunction
|
||||
public Class[] captureConvertedArgumentTypes(final String stringValue, final Integer integerValue,
|
||||
final Boolean booleanValue, final Person person, final Gender gender) {
|
||||
return getArgumentTypes(stringValue, integerValue, booleanValue, person, gender);
|
||||
}
|
||||
|
||||
@GemfireFunction
|
||||
public Class[] captureUnconvertedArgumentTypes(final String stringValue, final Integer integerValue,
|
||||
final Boolean booleanValue, final Object domainObject, final Object enumValue) {
|
||||
return getArgumentTypes(stringValue, integerValue, booleanValue, domainObject, enumValue);
|
||||
}
|
||||
|
||||
@GemfireFunction
|
||||
public String getAddressField(final PdxInstance address, final String fieldName) {
|
||||
Assert.isTrue(Address.class.getName().equals(address.getClassName()));
|
||||
return String.valueOf(address.getField(fieldName));
|
||||
}
|
||||
|
||||
@GemfireFunction
|
||||
public Object getDataField(final PdxInstance data, final String fieldName) {
|
||||
return data.getField(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Address {
|
||||
|
||||
private final String street;
|
||||
private final String city;
|
||||
private final String state; // refactor; use Enum!
|
||||
private final String zipCode;
|
||||
|
||||
public Address(final String street, final String city, final String state, final String zipCode) {
|
||||
Assert.hasText("The Address 'street' must be specified", street);
|
||||
Assert.hasText("The Address 'city' must be specified", city);
|
||||
Assert.hasText("The Address 'state' must be specified", state);
|
||||
Assert.hasText("The Address 'zipCode' must be specified", zipCode);
|
||||
this.street = street;
|
||||
this.city = city;
|
||||
this.state = state;
|
||||
this.zipCode = zipCode;
|
||||
}
|
||||
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public String getZipCode() {
|
||||
return zipCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof Address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Address that = (Address) obj;
|
||||
|
||||
return ObjectUtils.nullSafeEquals(this.getStreet(), that.getStreet())
|
||||
&& ObjectUtils.nullSafeEquals(this.getCity(), that.getCity())
|
||||
&& ObjectUtils.nullSafeEquals(this.getState(), that.getState())
|
||||
&& ObjectUtils.nullSafeEquals(this.getZipCode(), that.getZipCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashValue = 17;
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getStreet());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getCity());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getState());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getZipCode());
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%1$s %2$s, %3$s %4$s", getStreet(), getCity(), getState(), getZipCode());
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Gender {
|
||||
FEMALE,
|
||||
MALE
|
||||
}
|
||||
|
||||
public static class Person {
|
||||
|
||||
private final String firstName;
|
||||
private final String lastName;
|
||||
|
||||
public Person(final String firstName, final String lastName) {
|
||||
Assert.hasText(firstName, "The person's first name must be specified!");
|
||||
Assert.hasText(lastName, "The person's last name must be specified!");
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof Person)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Person that = (Person) obj;
|
||||
|
||||
return ObjectUtils.nullSafeEquals(this.getFirstName(), that.getFirstName())
|
||||
&& ObjectUtils.nullSafeEquals(this.getLastName(), that.getLastName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashValue = 17;
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getFirstName());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getLastName());
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%1$s %2$s", getFirstName(), getLastName());
|
||||
}
|
||||
}
|
||||
|
||||
public static class ComposablePdxSerializer implements PdxSerializer {
|
||||
|
||||
private final PdxSerializer[] pdxSerializers;
|
||||
|
||||
private ComposablePdxSerializer(final PdxSerializer[] pdxSerializers) {
|
||||
this.pdxSerializers = pdxSerializers;
|
||||
}
|
||||
|
||||
public static PdxSerializer compose(final PdxSerializer... pdxSerializers) {
|
||||
return (pdxSerializers == null ? null : (pdxSerializers.length == 1 ? pdxSerializers[0]
|
||||
: new ComposablePdxSerializer(pdxSerializers)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean toData(final Object obj, final PdxWriter out) {
|
||||
for (PdxSerializer pdxSerializer : pdxSerializers) {
|
||||
if (pdxSerializer.toData(obj, out)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromData(final Class<?> type, final PdxReader in) {
|
||||
for (PdxSerializer pdxSerializer : pdxSerializers) {
|
||||
Object obj = pdxSerializer.fromData(type, in);
|
||||
|
||||
if (obj != null) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ComposablePdxSerializerFactoryBean implements FactoryBean<PdxSerializer>, InitializingBean {
|
||||
|
||||
private List<PdxSerializer> pdxSerializers = Collections.emptyList();
|
||||
|
||||
private PdxSerializer pdxSerializer;
|
||||
|
||||
public void setPdxSerializers(final List<PdxSerializer> pdxSerializers) {
|
||||
this.pdxSerializers = pdxSerializers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
pdxSerializer = ComposablePdxSerializer.compose(pdxSerializers.toArray(
|
||||
new PdxSerializer[pdxSerializers.size()]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PdxSerializer getObject() throws Exception {
|
||||
return pdxSerializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return (pdxSerializer != null ? pdxSerializer.getClass() : PdxSerializer.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AddressPdxSerializer implements PdxSerializer {
|
||||
|
||||
@Override
|
||||
public boolean toData(final Object obj, final PdxWriter out) {
|
||||
if (obj instanceof Address) {
|
||||
Address address = (Address) obj;
|
||||
out.writeString("street", address.getStreet());
|
||||
out.writeString("city", address.getCity());
|
||||
out.writeString("state", address.getState());
|
||||
out.writeString("zipCode", address.getZipCode());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromData(final Class<?> type, final PdxReader in) {
|
||||
if (Address.class.isAssignableFrom(type)) {
|
||||
return new Address(in.readString("street"), in.readString("city"), in.readString("state"),
|
||||
in.readString("zipCode"));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static class PersonPdxSerializer implements PdxSerializer {
|
||||
|
||||
@Override
|
||||
public boolean toData(final Object obj, final PdxWriter out) {
|
||||
if (obj instanceof Person) {
|
||||
Person person = (Person) obj;
|
||||
out.writeString("firstName", person.getFirstName());
|
||||
out.writeString("lastName", person.getLastName());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromData(final Class<?> type, final PdxReader in) {
|
||||
if (Person.class.isAssignableFrom(type)) {
|
||||
return new Person(in.readString("firstName"), in.readString("lastName"));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright 2010-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;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.CacheFactory;
|
||||
import com.gemstone.gemfire.cache.execute.FunctionContext;
|
||||
import com.gemstone.gemfire.internal.cache.GemFireCacheImpl;
|
||||
import com.gemstone.gemfire.pdx.PdxInstance;
|
||||
import com.gemstone.gemfire.pdx.PdxInstanceFactory;
|
||||
import com.gemstone.gemfire.pdx.PdxReader;
|
||||
import com.gemstone.gemfire.pdx.PdxSerializer;
|
||||
import com.gemstone.gemfire.pdx.PdxWriter;
|
||||
import com.gemstone.gemfire.pdx.internal.PdxInstanceEnum;
|
||||
import com.gemstone.gemfire.pdx.internal.PdxInstanceFactoryImpl;
|
||||
|
||||
/**
|
||||
* The PdxFunctionArgumentResolverTest class is a test suite of test cases testing the contract and functionality
|
||||
* of the PdxFunctionArgumentResolver class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.gemfire.function.PdxFunctionArgumentResolver
|
||||
* @see com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.execute.FunctionContext
|
||||
* @see com.gemstone.gemfire.pdx.PdxInstance
|
||||
* @see com.gemstone.gemfire.pdx.PdxSerializer
|
||||
* @see com.gemstone.gemfire.pdx.internal.PdxInstanceEnum
|
||||
* @since 1.5.2
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class PdxFunctionArgumentResolverTest {
|
||||
|
||||
private static Cache gemfireCache;
|
||||
|
||||
private PdxFunctionArgumentResolver functionArgumentResolver;
|
||||
|
||||
@BeforeClass
|
||||
public static void setupGemFire() {
|
||||
gemfireCache = new CacheFactory()
|
||||
.setPdxSerializer(new PersonPdxSerializer())
|
||||
.setPdxReadSerialized(true)
|
||||
.set("name", PdxFunctionArgumentResolverTest.class.getSimpleName())
|
||||
.set("mcast-port", "0")
|
||||
.set("log-level", "warning")
|
||||
.create();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
gemfireCache.close();
|
||||
gemfireCache = null;
|
||||
}
|
||||
|
||||
protected Method getMethod(final Class<?> type, final String methodName, final Class<?>... parameterTypes) {
|
||||
try {
|
||||
return type.getDeclaredMethod(methodName, parameterTypes);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(String.format(
|
||||
"Failed to get method (%1$s) with signature (%2$s) on Class type (%3$s)!", methodName,
|
||||
getMethodSignature(methodName, parameterTypes), type.getClass().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
protected Object getMethodSignature(final String methodName, final Class<?>... parameterTypes) {
|
||||
StringBuilder methodSignature = new StringBuilder(methodName);
|
||||
int count = 0;
|
||||
|
||||
methodSignature.append("(");
|
||||
|
||||
for (Class parameterType : parameterTypes) {
|
||||
methodSignature.append(count++ > 0 ? ", :" : ":").append(parameterType.getSimpleName());
|
||||
}
|
||||
|
||||
methodSignature.append("):Void");
|
||||
|
||||
return methodSignature.toString();
|
||||
}
|
||||
|
||||
protected void assertArguments(final Object[] expectedArguments, final Object[] actualArguments) {
|
||||
assertNotNull(actualArguments);
|
||||
assertNotSame(expectedArguments, actualArguments);
|
||||
assertEquals(expectedArguments.length, actualArguments.length);
|
||||
|
||||
for (int index = 0; index < expectedArguments.length; index++) {
|
||||
assertEquals(expectedArguments[index], actualArguments[index]);
|
||||
}
|
||||
}
|
||||
|
||||
protected Person createPerson(final String firstName, final String lastName, final Gender gender) {
|
||||
return new Person(firstName, lastName, gender);
|
||||
}
|
||||
|
||||
protected PdxInstance toPdxInstance(final Person person) {
|
||||
PdxInstanceFactory pdxInstanceFactory = gemfireCache.createPdxInstanceFactory(person.getClass().getName());
|
||||
pdxInstanceFactory.writeString("firstName", person.getFirstName());
|
||||
pdxInstanceFactory.writeString("lastName", person.getLastName());
|
||||
pdxInstanceFactory.writeObject("gender", person.getGender());
|
||||
return pdxInstanceFactory.create();
|
||||
}
|
||||
|
||||
protected PdxInstance toPdxInstance(final Map<String, Object> objectData) {
|
||||
PdxInstanceFactory pdxInstanceFactory = gemfireCache.createPdxInstanceFactory(objectData.get("@type").toString());
|
||||
|
||||
for (Map.Entry<String, Object> entry : objectData.entrySet()) {
|
||||
if (!"@type".equals(entry.getKey())) {
|
||||
pdxInstanceFactory.writeObject(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return pdxInstanceFactory.create();
|
||||
}
|
||||
|
||||
protected PdxInstance toPdxInstance(final Enum enumeratedType) {
|
||||
return PdxInstanceFactoryImpl.createPdxEnum(enumeratedType.getClass().getName(), enumeratedType.name(),
|
||||
enumeratedType.ordinal(), (GemFireCacheImpl) gemfireCache);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveSimpleFunctionArguments() {
|
||||
functionArgumentResolver = new PdxFunctionArgumentResolver() {
|
||||
@Override public Method getFunctionAnnotatedMethod() {
|
||||
return getMethod(FunctionExecutions.class, "simpleMethod", Boolean.class, Character.class,
|
||||
Integer.class, Double.class, String.class);
|
||||
}
|
||||
};
|
||||
|
||||
Object[] expectedArguments = { Boolean.TRUE, 'C', 123, Math.PI, "TEST" };
|
||||
|
||||
FunctionContext mockFunctionContext = mock(FunctionContext.class, "testResolveSimpleFunctionArguments");
|
||||
|
||||
when(mockFunctionContext.getArguments()).thenReturn(expectedArguments);
|
||||
|
||||
Object[] actualArguments = functionArgumentResolver.resolveFunctionArguments(mockFunctionContext);
|
||||
|
||||
assertArguments(expectedArguments, actualArguments);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveNonSerializedApplicationDomainTypeFunctionArguments() {
|
||||
functionArgumentResolver = new PdxFunctionArgumentResolver() {
|
||||
@Override public Method getFunctionAnnotatedMethod() {
|
||||
return getMethod(FunctionExecutions.class, "nonSerializedMethod", Boolean.class, Person.class,
|
||||
Character.class, Person.class, Integer.class, Double.class, Gender.class, String.class);
|
||||
}
|
||||
};
|
||||
|
||||
Object[] expectedArguments = { Boolean.TRUE, createPerson("Jon", "Doe", Gender.MALE), 'C',
|
||||
createPerson("Jane", "Doe", Gender.FEMALE), 123, Math.PI, Gender.FEMALE, "test" };
|
||||
|
||||
FunctionContext mockFunctionContext = mock(FunctionContext.class, "testResolveNonSerializedApplicationDomainTypeFunctionArguments");
|
||||
|
||||
when(mockFunctionContext.getArguments()).thenReturn(expectedArguments);
|
||||
|
||||
Object[] actualArguments = functionArgumentResolver.resolveFunctionArguments(mockFunctionContext);
|
||||
|
||||
assertArguments(expectedArguments, actualArguments);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveSerializedApplicationDomainTypeFunctionArguments() {
|
||||
functionArgumentResolver = new PdxFunctionArgumentResolver() {
|
||||
@Override public Method getFunctionAnnotatedMethod() {
|
||||
return getMethod(FunctionExecutions.class, "serializedMethod", Boolean.class, Person.class,
|
||||
String.class, Gender.class);
|
||||
}
|
||||
};
|
||||
|
||||
Person jackHandy = createPerson("Jack", "Handy", Gender.MALE);
|
||||
|
||||
Object[] serializedArguments = { Boolean.TRUE, toPdxInstance(jackHandy), "test", toPdxInstance(Gender.MALE) };
|
||||
Object[] expectedArguments = { Boolean.TRUE, jackHandy, "test", Gender.MALE };
|
||||
|
||||
FunctionContext mockFunctionContext = mock(FunctionContext.class, "testResolveSerializedApplicationDomainTypeFunctionArguments");
|
||||
|
||||
when(mockFunctionContext.getArguments()).thenReturn(serializedArguments);
|
||||
|
||||
Object[] actualArguments = functionArgumentResolver.resolveFunctionArguments(mockFunctionContext);
|
||||
|
||||
assertArguments(expectedArguments, actualArguments);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveUnnecessaryDeserializationFunctionArguments() {
|
||||
functionArgumentResolver = new PdxFunctionArgumentResolver() {
|
||||
@Override public Method getFunctionAnnotatedMethod() {
|
||||
return getMethod(FunctionExecutions.class, "unnecessaryDeserializationMethod", Boolean.class,
|
||||
Object.class, String.class, PdxInstanceEnum.class);
|
||||
}
|
||||
};
|
||||
|
||||
Person sandyHandy = createPerson("Sandy", "Handy", Gender.FEMALE);
|
||||
|
||||
Object[] expectedArguments = { Boolean.TRUE, toPdxInstance(sandyHandy), "test", toPdxInstance(Gender.FEMALE) };
|
||||
|
||||
FunctionContext mockFunctionContext = mock(FunctionContext.class, "testResolveUnnecessaryDeserializationFunctionArguments");
|
||||
|
||||
when(mockFunctionContext.getArguments()).thenReturn(expectedArguments);
|
||||
|
||||
Object[] actualArguments = functionArgumentResolver.resolveFunctionArguments(mockFunctionContext);
|
||||
|
||||
assertArguments(expectedArguments, actualArguments);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveUnresolvableApplicationDomainTypeFunctionArguments() {
|
||||
functionArgumentResolver = new PdxFunctionArgumentResolver() {
|
||||
@Override public Method getFunctionAnnotatedMethod() {
|
||||
return getMethod(FunctionExecutions.class, "unresolvableMethod", String.class, Object.class);
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> addressData = new HashMap<String, Object>(5);
|
||||
|
||||
addressData.put("@type", "org.example.Address");
|
||||
addressData.put("street", "100 Main St.");
|
||||
addressData.put("city", "Portland");
|
||||
addressData.put("state", "OR");
|
||||
addressData.put("zip", "12345");
|
||||
|
||||
Object[] expectedArguments = { "test", toPdxInstance(addressData) };
|
||||
|
||||
FunctionContext mockFunctionContext = mock(FunctionContext.class, "testResolveUnresolvableApplicationDomainTypeFunctionArguments");
|
||||
|
||||
when(mockFunctionContext.getArguments()).thenReturn(expectedArguments);
|
||||
|
||||
Object[] actualArguments = functionArgumentResolver.resolveFunctionArguments(mockFunctionContext);
|
||||
|
||||
assertArguments(expectedArguments, actualArguments);
|
||||
}
|
||||
|
||||
public static interface FunctionExecutions {
|
||||
|
||||
void simpleMethod(Boolean value1, Character value2, Integer value3, Double value4, String value5);
|
||||
|
||||
void nonSerializedMethod(Boolean value1, Person person1, Character value2, Integer value3, Person person2, Double value4, Gender gender, String value5);
|
||||
|
||||
void serializedMethod(Boolean value1, Person person, String value2, Gender gender);
|
||||
|
||||
void unnecessaryDeserializationMethod(Boolean value1, Object person, String value2, PdxInstanceEnum gender);
|
||||
|
||||
void unresolvableMethod(String value, Object pdxInstance);
|
||||
}
|
||||
|
||||
public static enum Gender {
|
||||
FEMALE,
|
||||
MALE
|
||||
}
|
||||
|
||||
public static class Person {
|
||||
|
||||
private final Gender gender;
|
||||
|
||||
private final String firstName;
|
||||
private final String lastName;
|
||||
|
||||
public Person(final String firstName, final String lastName, final Gender gender) {
|
||||
Assert.hasText(firstName, "The person's first name must be specified!");
|
||||
Assert.hasText(lastName, "The person's last name must be specified!");
|
||||
Assert.notNull(gender, "The person's gender must be specified!");
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public Gender getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof Person)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Person that = (Person) obj;
|
||||
|
||||
return ObjectUtils.nullSafeEquals(this.getFirstName(), that.getFirstName())
|
||||
&& ObjectUtils.nullSafeEquals(this.getLastName(), that.getLastName())
|
||||
&& ObjectUtils.nullSafeEquals(this.getGender(), that.getGender());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashValue = 17;
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getFirstName());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getLastName());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getGender());
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%1$s %2$s is a %3$s", getFirstName(), getLastName(), getGender());
|
||||
}
|
||||
}
|
||||
|
||||
public static class PersonPdxSerializer implements PdxSerializer {
|
||||
|
||||
@Override
|
||||
public boolean toData(final Object obj, final PdxWriter out) {
|
||||
if (obj instanceof Person) {
|
||||
Person person = (Person) obj;
|
||||
out.writeString("firstName", person.getFirstName());
|
||||
out.writeString("lastName", person.getLastName());
|
||||
out.writeObject("gender", person.getGender());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromData(final Class<?> type, final PdxReader in) {
|
||||
if (Person.class.isAssignableFrom(type)) {
|
||||
return new Person(in.readString("firstName"), in.readString("lastName"),
|
||||
(Gender) in.readObject("gender"));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-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.sample;
|
||||
|
||||
import org.springframework.data.gemfire.function.ClientCacheFunctionExecutionWithPdxIntegrationTest;
|
||||
import org.springframework.data.gemfire.function.annotation.OnServer;
|
||||
|
||||
import com.gemstone.gemfire.pdx.PdxInstance;
|
||||
|
||||
/**
|
||||
* The ApplicationDomainFunctionExecutions class defines a GemFire Client Cache Function execution targeted at a
|
||||
* GemFire Server.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.gemfire.function.ClientCacheFunctionExecutionWithPdxIntegrationTest
|
||||
* @see org.springframework.data.gemfire.function.annotation.OnServer
|
||||
* @see com.gemstone.gemfire.pdx.PdxInstance
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@OnServer
|
||||
@SuppressWarnings("unused")
|
||||
public interface ApplicationDomainFunctionExecutions {
|
||||
|
||||
Class[] captureConvertedArgumentTypes(String stringValue, Integer integerValue, Boolean booleanValue,
|
||||
ClientCacheFunctionExecutionWithPdxIntegrationTest.Person person,
|
||||
ClientCacheFunctionExecutionWithPdxIntegrationTest.Gender gender);
|
||||
|
||||
Class[] captureUnconvertedArgumentTypes(String stringValue, Integer integerValue, Boolean booleanValue,
|
||||
Object person, Object gender);
|
||||
|
||||
String getAddressField(ClientCacheFunctionExecutionWithPdxIntegrationTest.Address address, String fieldName);
|
||||
|
||||
Object getDataField(PdxInstance data, String fieldName);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
||||
xmlns:gfe-data="http://www.springframework.org/schema/data/gemfire"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
||||
http://www.springframework.org/schema/data/gemfire http://www.springframework.org/schema/data/gemfire/spring-data-gemfire.xsd
|
||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
||||
">
|
||||
|
||||
<util:properties id="clientProperties">
|
||||
<prop key="client.server.host">localhost</prop>
|
||||
<prop key="client.server.port">12480</prop>
|
||||
</util:properties>
|
||||
|
||||
<context:property-placeholder properties-ref="clientProperties"/>
|
||||
|
||||
<util:properties id="gemfireProperties">
|
||||
<prop key="log-level">config</prop>
|
||||
</util:properties>
|
||||
|
||||
<gfe:pool id="serverConnectionPool">
|
||||
<gfe:server host="${client.server.host}" port="${client.server.port}"/>
|
||||
</gfe:pool>
|
||||
|
||||
<bean id="addressPdxSerializer" class="org.springframework.data.gemfire.function.ClientCacheFunctionExecutionWithPdxIntegrationTest$AddressPdxSerializer"/>
|
||||
|
||||
<bean id="personPdxSerializer" class="org.springframework.data.gemfire.function.ClientCacheFunctionExecutionWithPdxIntegrationTest$PersonPdxSerializer"/>
|
||||
|
||||
<bean id="domainBasedPdxSerializer" class="org.springframework.data.gemfire.function.ClientCacheFunctionExecutionWithPdxIntegrationTest$ComposablePdxSerializerFactoryBean">
|
||||
<property name="pdxSerializers">
|
||||
<list>
|
||||
<ref bean="addressPdxSerializer"/>
|
||||
<ref bean="personPdxSerializer"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<gfe:client-cache properties-ref="gemfireProperties" pool-name="serverConnectionPool" pdx-serializer-ref="domainBasedPdxSerializer"/>
|
||||
|
||||
<gfe-data:function-executions base-package="org.springframework.data.gemfire.function.sample">
|
||||
<gfe-data:include-filter type="assignable" expression="org.springframework.data.gemfire.function.sample.ApplicationDomainFunctionExecutions"/>
|
||||
</gfe-data:function-executions>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
||||
">
|
||||
|
||||
<util:properties id="serverProperties">
|
||||
<prop key="server.bind.address">localhost</prop>
|
||||
<prop key="server.hostname.for.clients">localhost</prop>
|
||||
<prop key="server.max.connections">2</prop>
|
||||
<prop key="server.port">12480</prop>
|
||||
</util:properties>
|
||||
|
||||
<context:property-placeholder properties-ref="serverProperties"/>
|
||||
|
||||
<util:properties id="gemfireProperties">
|
||||
<prop key="name">SpringGemFireServerFunctionCreationWithPdx</prop>
|
||||
<prop key="mcast-port">0</prop>
|
||||
<prop key="log-level">warning</prop>
|
||||
</util:properties>
|
||||
|
||||
<bean id="addressPdxSerializer" class="org.springframework.data.gemfire.function.ClientCacheFunctionExecutionWithPdxIntegrationTest$AddressPdxSerializer"/>
|
||||
|
||||
<bean id="personPdxSerializer" class="org.springframework.data.gemfire.function.ClientCacheFunctionExecutionWithPdxIntegrationTest$PersonPdxSerializer"/>
|
||||
|
||||
<bean id="domainBasedPdxSerializer" class="org.springframework.data.gemfire.function.ClientCacheFunctionExecutionWithPdxIntegrationTest$ComposablePdxSerializerFactoryBean">
|
||||
<property name="pdxSerializers">
|
||||
<list>
|
||||
<ref bean="addressPdxSerializer"/>
|
||||
<ref bean="personPdxSerializer"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<gfe:cache properties-ref="gemfireProperties" pdx-serializer-ref="domainBasedPdxSerializer"
|
||||
pdx-read-serialized="true" pdx-ignore-unread-fields="false"/>
|
||||
|
||||
<gfe:cache-server auto-startup="true" bind-address="${server.bind.address}" port="${server.port}"
|
||||
host-name-for-clients="${server.hostname.for.clients}" max-connections="${server.max.connections}"/>
|
||||
|
||||
<gfe:annotation-driven/>
|
||||
|
||||
<bean class="org.springframework.data.gemfire.function.ClientCacheFunctionExecutionWithPdxIntegrationTest$ApplicationDomainFunctions"/>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user