SGF-636 - Add support for GemFire JCA ResourceAdapter.

(cherry picked from commit 344ae601dd)
Signed-off-by: John Blum <jblum@pivotal.io>
This commit is contained in:
John Blum
2017-06-30 18:38:41 -07:00
parent 9ce135d906
commit a2530ea489
15 changed files with 2720 additions and 8 deletions

View File

@@ -65,24 +65,253 @@ Since 1.1, Spring Data GemFire provides an implementation of the Spring 3.1 http
</beans>
----
[[apis:tx-mgmt]]
== Transaction Management
[[apis:transaction-management]]
== Local, Cache Transaction Management
One of the most popular features of Spring Framework is http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#transaction[transaction management]. If you are not familiar with it, we strongly recommend http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#transaction-motivation[reading] about it as it offers a consistent programming model that works transparently across multiple APIs and can be configured either programmatically or declaratively (the most popular choice).
One of the most popular features of the _Spring Framework_ is
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#transaction[Transaction Management].
For GemFire, Spring Data GemFire provides a dedicated, per-cache, transaction manager that, once declared, allows Region operations to be executed atomically through Spring:
If you are not familiar with _Spring's_ transaction abstraction then we strongly recommend
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#transaction-motivation[reading]
about _Spring's Transaction Management_ infrastructure as it offers a consistent _programming model_ that works
transparently across multiple APIs and can be configured either programmatically or declaratively
(the most popular choice).
For GemFire, _Spring Data GemFire_ provides a dedicated, per-cache, transaction manager that, once declared,
allows Region operations to be executed atomically through Spring:
[source,xml]
----
<gfe:transaction-manager id="tx-manager" cache-ref="cache"/>
----
NOTE: The example above can be simplified even more by eliminating the `cache-ref` attribute if the GemFire Cache is defined under the default name`gemfireCache`. As with the other Spring Data GemFire namespace elements, if the Cache bean name is not configured, the aforementioned naming convention will used. Additionally, the transaction manager name is`gemfireTransactionManager` if not explicitly specified.
NOTE: The example above can be simplified even further by eliminating the `cache-ref` attribute if the GemFire cache
is defined under the default name, `gemfireCache`. As with the other _Spring Data GemFire_ namespace elements,
if the cache bean name is not configured, the aforementioned naming convention will be used.
Additionally, the transaction manager name is "`gemfireTransactionManager`" if not explicitly specified.
Currently, GemFire supports optimistic transactions with *read committed* isolation. Furthermore, to guarantee this isolation, developers should avoid making *in-place* changes that manually modify values present in the Cache. To prevent this from happening, the transaction manager configures the Cache to use *copy on read* semantics, meaning a clone of the actual value is created, each time a read is performed. This behavior can be disabled if needed through the `copyOnRead` property. For more information on the semantics of the underlying GemFire transaction manager, see the GemFire http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/CacheTransactionManager.html[documentation].
Currently, Pivotal GemFire supports optimistic transactions with *read committed* isolation. Furthermore, to guarantee
this isolation, developers should avoid making *in-place* changes that manually modify values present in the cache.
To prevent this from happening, the transaction manager configures the cache to use *copy on read* semantics by default,
meaning a clone of the actual value is created each time a read is performed. This behavior can be disabled if needed
through the `copyOnRead` property.
For more information on the semantics and behavior of the underlying Geode transaction manager, please refer to the Geode
http://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/CacheTransactionManager.html[CacheTransactionManager Javadoc]
as well as the http://geode.apache.org/docs/guide/11/developing/transactions/chapter_overview.html[documentation].
[[apis:global-transaction-management]]
== Global, JTA Transaction Management
It is also possible for Pivotal GemFire to participate in a Global, JTA based transaction, such as a transaction managed
by an Java EE Application Server (e.g. WebSphere Application Server, a.k.a. WAS) using Container Managed Transactions
(CMT) along with other JTA resources.
However, unlike many other JTA "compliant" resources (e.g. JMS Message Brokers like ActiveMQ), Pivotal GemFire is *not*
an XA compliant resource. Therefore, Pivotal GemFire must be positioned as the "_Last Resource_" in a JTA transaction
(_prepare phase_) since it does not implement the 2-phase commit protocol, or rather does not handle
distributed transactions.
Many managed environments with CMT maintain support for "_Last Resource_", non-XA compliant resources in JTA transactions
though it is not actually required in the JTA spec. More information on what a non-XA compliant, "_Last Resource_" means
can be found in Red Hat's https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/5/html/Administration_And_Configuration_Guide/lrco-overview.html[documentation].
In fact, Red Hat's JBoss project, http://narayana.io/[Narayana] is one such LGPL Open Source implementation. _Narayana_
refers to this as "_Last Resource Commit Optimization_" (LRCO). More details can be found
http://narayana.io//docs/project/index.html#d0e1859[here].
However, whether you are using Pivotal GemFire in a standalone environment with an Open Source JTA Transaction Management
implementation that supports "_Last Resource_", or a managed environment (e.g. Java EE AS such as WAS),
_Spring Data Geode_ has you covered.
There are a series of steps you must complete to properly use Pivotal GemFire as a "_Last Resource_" in a JTA transaction
involving more than 1 transactional resource. Additionally, there can only be 1 non-XA compliant resource
(e.g. Pivotal GemFire) in such an arrangement.
1) First, you must complete Steps 1-4 in GemFire's documentation
http://gemfire90.docs.pivotal.io/geode/developing/transactions/JTA_transactions.html#concept_csy_vfb_wk[here].
NOTE: #1 above is independent of your _Spring [Boot] and/or [Data GemFire]_ application
and must be completed successfully.
2) Referring to Step 5 in GemFire's http://gemfire90.docs.pivotal.io/geode/developing/transactions/JTA_transactions.html#concept_csy_vfb_wk[documentation],
_Spring Data GemFire's_ Annotation support will attempt to set the `GemFireCache`, http://gemfire-90-javadocs.docs.pivotal.io/org/apache/geode/cache/GemFireCache.html#setCopyOnRead-boolean-[`copyOnRead`]
property for you when using the `@EnableGemFireAsLastResource` annotation.
However, if SDG's auto-configuration is unsuccessful then you must explicitly set the `copy-on-read` attribute on the
`<gfe:cache>` or `<gfe:client-cache>` element in XML or the `copyOnRead` property of the SDG `CacheFactoryBean` class
in JavaConfig to *true*. For example...
Peer Cache XML:
[source,xml]
----
<gfe:cache ... copy-on-read="true"/>
----
Peer Cache JavaConfig:
[source,java]
----
@Bean
CacheFacatoryBean gemfireCache() {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setClose(true);
gemfireCache.setCopyOnRead(true);
return gemfireCache;
}
----
Client Cache XML:
[source,xml]
----
<gfe:client-cache ... copy-on-read="true"/>
----
Client Cache JavaConfig:
[source,java]
----
@Bean
ClientCacheFacatoryBean gemfireCache() {
ClientCacheFactoryBean gemfireCache = new ClientCacheFactoryBean();
gemfireCache.setClose(true);
gemfireCache.setCopyOnRead(true);
return gemfireCache;
}
----
NOTE: explicitly setting the `copy-on-read` attribute or optionally the `copyOnRead` property
really should not be necessary.
3) At this point, you *skip* Steps 6-8 in GemFire's http://gemfire90.docs.pivotal.io/geode/developing/transactions/JTA_transactions.html#concept_csy_vfb_wk[documentation]
and let _Spring Data Geode_ work its magic. All you need do is annotate your _Spring_ `@Configuration` class
with _Spring Data GemFire's_ *new* `@EnableGemFireAsLastResource` annotation and a combination of _Spring's_
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#transaction[Transaction Management]
infrastructure and _Spring Data GemFire's_ `@EnableGemFireAsLastResource` configuration does the trick.
The configuration looks like this...
[source,java]
----
@Configuration
@EnableGemFireAsLastResource
@EnableTransactionManagement(order = 1)
class GeodeConfiguration {
...
}
----
The only requirements are...
3.1) The `@EnableGemFireAsLastResource` annotation must be declared on the same _Spring_ `@Configuration` class
where _Spring's_ `@EnableTransactionManagement` annotation is also specified.
3.2) The `order` attribute of the `@EnableTransactionManagement` annotation must be explicitly set to an integer value
that is not `Integer.MAX_VALUE` or `Integer.MIN_VALUE` (defaults to `Integer.MAX_VALUE`).
Of course, hopefully you are aware that you also need to configure _Spring's_ `JtaTransactionManager`
when using JTA Transactions like so..
[source,java]
----
@Bean
public JtaTransactionManager transactionManager(UserTransaction userTransaction) {
JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setUserTransaction(userTransaction);
return transactionManager;
}
----
NOTE: The configuration in section <<apis:transaction-management>> does *not* apply here.
The use of _Spring Data GemFire's_ `GemfireTransactionManager` is applicable only in "Local", Cache Transactions,
*not* "Global", JTA Transactions. Therefore, you do *not* configure the SDG `GemfireTransactionManager` in this case.
You configure _Spring's_ `JtaTransactionManager` as shown above.
For more details on using _Spring's Transaction Management_ with JTA,
see http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#transaction-application-server-integration[here].
Effectively, _Spring Data GemFire's_ `@EnableGemFireAsLastResource` annotation imports configuration containing 2 Aspect
bean definitions that handles the GemFire `o.a.g.ra.GFConnectionFactory.getConnection()`
and `o.a.g.ra.GFConnection.close()` operations at the appropriate points during the transactional operation.
Specifically, the correct sequence of events are...
1. `jtaTransation.begin()`
2. `GFConnectionFactory.getConnection()`
3. Call the application's `@Transactional` service method
4. Either `jtaTransaction.commit()` or `jtaTransaction.rollback()`
5. Finally, `GFConnection.close()`
This is consistent with how you, as the application developer, would code this manually if you had to use the JTA API
+ GemFire API yourself, as shown in the
GemFire http://gemfire90.docs.pivotal.io/geode/developing/transactions/jca_adapter_example.html#concept_swv_z2p_wk[example].
Thankfully, _Spring_ does the heavy lifting for you and all you need do after applying the appropriate configuration
(shown above) is...
[source,java]
----
@Service
class MyTransactionalService ... {
@Transactional
public <Return-Type> someTransactionalMethod() {
// perform business logic interacting with and accessing multiple JTA resources atomically, here
}
...
}
----
#1 & #4 above are appropriately handled for you by _Spring's_ JTA based `PlatformTransactionManager` once the
`@Transactional` boundary is entered by your application (i.e. when the `MyTransactionSerivce.someTransactionalMethod()`
is called).
#2 & #3 are handled by _Spring Data GemFire's_ new Aspects enabled with the `@EnableGemFireAsLastResource` annotation.
#3 of course is the responsibility of your application.
Indeed, with the appropriate logging configured, you will see the correct sequence of events...
[source,xml]
----
2017-Jun-22 11:11:37 TRACE TransactionInterceptor - Getting transaction for [example.app.service.MessageService.send]
2017-Jun-22 11:11:37 TRACE GemFireAsLastResourceConnectionAcquiringAspect - Acquiring GemFire Connection
from GemFire JCA ResourceAdapter registered at [gfe/jca]
2017-Jun-22 11:11:37 TRACE MessageService - PRODUCER [ Message :
[{ @type = example.app.domain.Message, id= MSG0000000000, message = SENT }],
JSON : [{"id":"MSG0000000000","message":"SENT"}] ]
2017-Jun-22 11:11:37 TRACE TransactionInterceptor - Completing transaction for [example.app.service.MessageService.send]
2017-Jun-22 11:11:37 TRACE GemFireAsLastResourceConnectionClosingAspect - Closed GemFire Connection @ [Reference [...]]
----
For more details on using Pivotal GemFire in JTA transactions,
see http://gemfire90.docs.pivotal.io/geode/developing/transactions/JTA_transactions.html[here].
For more details on configuring Pivotal GemFire as a "_Last Resource_",
see http://gemfire90.docs.pivotal.io/geode/developing/transactions/JTA_transactions.html#concept_csy_vfb_wk[here].
:leveloffset: +1
include::{basedocdir}/reference/cq-container.adoc[]
:leveloffset: -1
[[apis:declarable]]

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
/**
* The {@link EnableGemFireAsLastResource} annotation is used to enable GemFire as a Last Resource in a Spring,
* CMT/JTA Transaction.
*
* @author John Blum
* @see org.springframework.context.annotation.EnableAspectJAutoProxy
* @see org.springframework.context.annotation.Import
* @see org.springframework.data.gemfire.config.annotation.GemFireAsLastResourceConfiguration
* @since 2.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableAspectJAutoProxy
@Import(GemFireAsLastResourceConfiguration.class)
@SuppressWarnings("unused")
public @interface EnableGemFireAsLastResource {
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
/**
* The EnableRegionDataAccessTracing class...
*
* @author John Blum
* @since 1.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableAspectJAutoProxy
@Import(RegionDataAccessTracingConfiguration.class)
@SuppressWarnings("unused")
public @interface EnableRegionDataAccessTracing {
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation;
import java.util.Map;
import com.gemstone.gemfire.cache.GemFireCache;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.config.annotation.support.GemFireAsLastResourceConnectionAcquiringAspect;
import org.springframework.data.gemfire.config.annotation.support.GemFireAsLastResourceConnectionClosingAspect;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.util.Assert;
/**
* The {@link GemFireAsLastResourceConfiguration} class is a Spring {@link Configuration @Configuration}
* annotated class used to configure the GemFire "Last Resource" Spring Data GemFire {@link Aspect Aspects}.
*
* @author John Blum
* @see org.aspectj.lang.annotation.Aspect
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.ImportAware
* @see org.springframework.core.annotation.AnnotationAttributes
* @see org.springframework.core.type.AnnotationMetadata
* @see org.springframework.data.gemfire.config.annotation.EnableGemFireAsLastResource
* @see org.springframework.data.gemfire.config.annotation.support.GemFireAsLastResourceConnectionAcquiringAspect
* @see org.springframework.data.gemfire.config.annotation.support.GemFireAsLastResourceConnectionClosingAspect
* @see org.springframework.transaction.annotation.EnableTransactionManagement
* @see com.gemstone.gemfire.cache.GemFireCache
* @since 1.9.0
*/
@Configuration
@SuppressWarnings("unused")
public class GemFireAsLastResourceConfiguration implements ImportAware {
private Integer enableTransactionManagementOrder;
/* (non-Javadoc) */
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.enableTransactionManagementOrder = resolveEnableTransactionManagementOrder(importMetadata);
}
/* (non-Javadoc) */
@SuppressWarnings("all")
protected int resolveEnableTransactionManagementOrder(AnnotationMetadata importMetadata) {
AnnotationAttributes enableTransactionManagementAttributes =
resolveEnableTransactionManagementAttributes(importMetadata);
Integer order = enableTransactionManagementAttributes.getNumber("order");
return validateOrder(order);
}
/* (non-Javadoc) */
private Integer validateOrder(Integer order) {
if (isInvalidOrder(order)) {
throw new IllegalArgumentException(String.format(
"The @%1$s(order) attribute value [%2$s] must be explicitly set to a value other than Integer.MAX_VALUE"
+ " or Integer.MIN_VALUE", EnableTransactionManagement.class.getSimpleName(),
String.valueOf(order)));
}
return order;
}
/* (non-Javadoc) */
private boolean isInvalidOrder(Integer order) {
return !isValidOrder(order);
}
/* (non-Javadoc) */
private boolean isValidOrder(Integer order) {
return !(order == null || order == Integer.MAX_VALUE || order == Integer.MIN_VALUE);
}
/* (non-Javadoc) */
@SuppressWarnings("all")
protected AnnotationAttributes resolveEnableTransactionManagementAttributes(
AnnotationMetadata importMetadata) {
Map<String, Object> enableTransactionManagementAttributes =
importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName());
return validate(enableTransactionManagementAttributes);
}
/* (non-Javadoc) */
private AnnotationAttributes validate(Map<String, Object> enableTransactionManagementAttributes) {
Assert.state(enableTransactionManagementAttributes != null,
String.format("The @%1$s annotation may only be used on a Spring application @%2$s class"
+ " that is also annotated with @%3$s having an explicit [order] set",
EnableGemFireAsLastResource.class.getSimpleName(), Configuration.class.getSimpleName(),
EnableTransactionManagement.class.getSimpleName()));
return AnnotationAttributes.fromMap(enableTransactionManagementAttributes);
}
/* (non-Javadoc) */
protected Integer getEnableTransactionManagementOrder() {
Assert.state(this.enableTransactionManagementOrder != null,
String.format("The @%1$s(order) attribute was not properly set [%2$s]; Also, please make your"
+ " Spring application @%3$s annotated class is annotated with both @%4$s and @%1$s",
EnableTransactionManagement.class.getSimpleName(), this.enableTransactionManagementOrder,
Configuration.class.getSimpleName(), EnableGemFireAsLastResource.class.getSimpleName()));
return this.enableTransactionManagementOrder;
}
/* (non-Javadoc) */
@Bean
public Object gemfireCachePostProcessor(@Autowired(required = false) GemFireCache gemfireCache) {
if (gemfireCache != null) {
gemfireCache.setCopyOnRead(true);
}
return null;
}
/* (non-Javadoc) */
@Bean
public GemFireAsLastResourceConnectionAcquiringAspect gemfireJcaConnectionAcquiringAspect() {
GemFireAsLastResourceConnectionAcquiringAspect connectionAcquiringAspect =
new GemFireAsLastResourceConnectionAcquiringAspect();
int order = (getEnableTransactionManagementOrder() + 1);
connectionAcquiringAspect.setOrder(order);
return connectionAcquiringAspect;
}
/* (non-Javadoc) */
@Bean
public GemFireAsLastResourceConnectionClosingAspect gemfireJcaConnectionClosingAspect() {
GemFireAsLastResourceConnectionClosingAspect connectionClosingAspect =
new GemFireAsLastResourceConnectionClosingAspect();
int order = (getEnableTransactionManagementOrder() - 1);
connectionClosingAspect.setOrder(order);
return connectionClosingAspect;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.config.annotation.support.RegionDataAccessTracingAspect;
/**
* The RegionDataAccessTracingConfiguration class...
*
* @author John Blum
* @since 1.0.0
*/
@Configuration
@SuppressWarnings("unused")
public class RegionDataAccessTracingConfiguration {
/* (non-Javadoc) */
@Bean
public RegionDataAccessTracingAspect regionDataAccessTracingAspect() {
return new RegionDataAccessTracingAspect();
}
}

View File

@@ -0,0 +1,600 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation.support;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.resource.ResourceException;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.ra.GFConnection;
import com.gemstone.gemfire.ra.GFConnectionFactory;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.util.StringUtils;
/**
* {@link AbstractGemFireAsLastResourceAspectSupport} is an abstract base class encapsulating functionality common
* to all AOP Aspect extensions/implementations involving the GemFire JCA ResourceAdapter object registered in
* the JNDI context of a managed environment.
*
* @author John Blum
* @see javax.naming.Context
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.ra.GFConnection
* @see org.slf4j.Logger
* @see org.springframework.core.Ordered
* @since 1.0.0
*/
@SuppressWarnings("unused")
public class AbstractGemFireAsLastResourceAspectSupport implements Ordered {
protected static final boolean DEFAULT_THROW_ON_ERROR = false;
protected static final int DEFAULT_ORDER = Integer.MAX_VALUE;
protected static final Consumer<String> NO_OP_LOGGER = new Consumer<String>() {
@Override
public void accept(String obj) {
}
};
protected static final String DEFAULT_GEMFIRE_JCA_RESOURCE_ADAPTER_JNDI_NAME = "gfe/jca";
private boolean throwOnError = DEFAULT_THROW_ON_ERROR;
private int order = DEFAULT_ORDER;
@Autowired(required = false)
private Context context;
@Autowired(required = false)
private GemFireCache gemfireCache;
private final Logger logger = newLogger();
@Value("${spring.data.gemfire.jca.resource-adapter.jndi.name:"
+ DEFAULT_GEMFIRE_JCA_RESOURCE_ADAPTER_JNDI_NAME + "}")
private String gemfireJcaResourceAdapterJndiName;
@Value("${spring.data.gemfire.naming.context.factory:}")
private String initialContextFactory;
@Value("${spring.data.gemfire.naming.context.provider-url:}")
private String providerUrl;
/**
* Returns a reference to the naming {@link Context}.
*
* @return a reference to the naming {@link Context}.
* @see javax.naming.Context
*/
protected synchronized Context getContext() {
return this.context;
}
/**
* Returns a reference to the {@link GemFireCache} used to interact with GemFire.
*
* @param <T> {@link Class} sub-type of the {@link GemFireCache} in use.
* @return a reference to the {@link GemFireCache}.
* @see com.gemstone.gemfire.cache.GemFireCache
*/
@SuppressWarnings("unchecked")
protected synchronized <T extends GemFireCache> T getGemFireCache() {
return (T) this.gemfireCache;
}
/**
* Returns the configured reference to GemFire's JCA ResourceAdapter registered in the managed environment's
* JNDI context.
*
* @return a {@link String} containing the configured reference to GemFire's JCA ResourceAdapter
* registered in the managed environment's JNDI context.
*/
public String getGemFireJcaResourceAdapterJndiName() {
return this.gemfireJcaResourceAdapterJndiName;
}
/**
* Returns the configured, fully-qualified classname of the {@link javax.naming.spi.InitialContextFactory}
* used to construct the {@link InitialContext} that is then used to lookup managed objects registered
* in the JNDI context of the managed environment.
*
* @return the configured, fully-qualified classname of the {@link javax.naming.spi.InitialContextFactory}
* used to construct the {@link InitialContext}.
*/
public String getInitialContextFactory() {
return this.initialContextFactory;
}
/**
* Returns a reference to the {@link Logger} used to record debug, info, warning and error messages
* logged by the application.
*
* @return a reference to the configured {@link Logger} used by the application for logging purposes.
* @see org.slf4j.Logger
*/
protected Logger getLogger() {
return this.logger;
}
/**
* Sets the order of this AOP Aspect relative to other Aspects in the chain of Aspects configured
* in Spring's Transaction Management.
*
* @param order int value specifying the relative order of this Aspect.
* @see org.springframework.core.Ordered
*/
public void setOrder(int order) {
this.order = order;
}
/**
* Returns the order of this AOP Aspect relative to other Aspects in the chain of Aspects configured
* in Spring's Transaction Management.
*
* @return an int value specifying the relative order of this Aspect.
* @see org.springframework.core.Ordered
*/
@Override
public int getOrder() {
int order = this.order;
return (!(order == Integer.MAX_VALUE || order == Integer.MIN_VALUE) ? order : getDefaultOrder());
}
/**
* Returns the default order used by this AOP Aspect in the chain of Aspects configured
* in Spring's Transaction Management.
*
* @return an int value specifying the default order used by this AOP Aspect in the chain of Aspects
* configured in Spring's Transaction Management.
*/
protected Integer getDefaultOrder() {
return DEFAULT_ORDER;
}
/**
* Returns the URL of the Naming Context Provider as a {@link String}.
*
* @return the URL of the Naming Context Provider.
*/
public String getProviderUrl() {
return this.providerUrl;
}
/**
* Determines whether an Exception should be thrown when this Aspect is unable to perform its function.
*
* Alternatively (and by default) the error condition is simply logged.
*
* Defaults to {@literal false}.
*
* @return a boolean value indicating whether this Aspect should throw an Exception when an error occurs
* preventing this Aspect from performing its normal function.
*/
public boolean isThrowOnError() {
return this.throwOnError;
}
/**
* Defines an AOP Pointcut identifying Join Points during the execution of the application's components
* on which this Aspect should be applied.
*
* This particular Pointcut identifies {@link org.springframework.transaction.annotation.Transactional} annotated
* application service {@link Class types}. That is, the {@link org.springframework.transaction.annotation.Transactional}
* annotation is used at the class level.
*
* @see org.aspectj.lang.annotation.Pointcut
*/
@Pointcut("@within(org.springframework.transaction.annotation.Transactional)")
protected void atTransactionalType() {}
/**
* Defines an AOP Pointcut identifying Join Points during the execution of the application's components
* on which this Aspect should be applied.
*
* This particular Pointcut identifies {@link org.springframework.transaction.annotation.Transactional} annotated
* application service {@link java.lang.reflect.Method methods}. That is, the
* {@link org.springframework.transaction.annotation.Transactional} annotation is used at the service class,
* service {@link java.lang.reflect.Method method} level.
*
* @see org.aspectj.lang.annotation.Pointcut
*/
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
protected void atTransactionalMethod() {}
/**
* Formats the given {@link String message} with the provided array of {@link Object arguments}.
*
* @param message {@link String} containing the message to format.
* @param args array of {@link Object arguments} used to format the message.
* @return the {@link String message} formatted with the provided array of {@link Object arguments}.
* @see java.lang.String#format(String, Object...)
*/
protected String format(String message, Object... args) {
return String.format(message, args);
}
/**
* Logs the given {@link String message} formatted with the given array of {@link Object arguments}
* at {@link Level#DEBUG} level when debugging is enabled.
*
* @param <T> {@link Class} type extension of {@link AbstractGemFireAsLastResourceAspectSupport}.
* @param message {@link String} containing the message to log.
* @param args array of {@link Object arguments} used to format the message.
* @return this aspect.
* @see org.slf4j.Logger#isDebugEnabled()
* @see org.slf4j.Logger#debug(String, Object...)
* @see #format(String, Object...)
*/
@SuppressWarnings("unchecked")
protected <T extends AbstractGemFireAsLastResourceAspectSupport> T logDebugInfo(String message, Object... args) {
Logger logger = getLogger();
if (logger.isDebugEnabled()) {
logger.debug(format(message, args), args);
}
return (T) this;
}
/**
* Logs the given {@link String message} formatted with the given array of {@link Object arguments}
* at {@link Level#INFO} level when info logging is enabled.
*
* @param <T> {@link Class} type extension of {@link AbstractGemFireAsLastResourceAspectSupport}.
* @param message {@link String} containing the message to log.
* @param args array of {@link Object arguments} used to format the message.
* @return this aspect.
* @see org.slf4j.Logger#isInfoEnabled()
* @see org.slf4j.Logger#info(String, Object...)
* @see #format(String, Object...)
*/
@SuppressWarnings("unchecked")
protected <T extends AbstractGemFireAsLastResourceAspectSupport> T logInfo(String message, Object... args) {
Logger logger = getLogger();
if (logger.isInfoEnabled()) {
logger.info(format(message, args), args);
}
return (T) this;
}
/**
* Logs the given {@link String message} formatted with the given array of {@link Object arguments}
* at {@link Level#TRACE} level when tracing is enabled.
*
* @param <T> {@link Class} type extension of {@link AbstractGemFireAsLastResourceAspectSupport}.
* @param message {@link String} containing the message to log.
* @param args array of {@link Object arguments} used to format the message.
* @return this aspect.
* @see #logTraceInfo(Supplier)
*/
protected <T extends AbstractGemFireAsLastResourceAspectSupport> T logTraceInfo(final String message,
final Object... args) {
return logTraceInfo(new Supplier<String>() {
@Override
public String get() {
return format(message, args);
}
});
}
/**
* Logs the given {@link Supplier log message} at {@link Level#TRACE} level when tracing is enabled.
*
* @param <T> {@link Class} type extension of {@link AbstractGemFireAsLastResourceAspectSupport}.
* @param logMessage {@link Supplier} of the message to log.
* @return this aspect.
* @see org.slf4j.Logger#isTraceEnabled()
* @see org.slf4j.Logger#trace(String)
*/
@SuppressWarnings("unchecked")
protected <T extends AbstractGemFireAsLastResourceAspectSupport> T logTraceInfo(Supplier<String> logMessage) {
Logger logger = getLogger();
if (logger.isTraceEnabled()) {
logger.trace(logMessage.get());
}
return (T) this;
}
/**
* Logs the given {@link String message} formatted with the given array of {@link Object arguments}
* at {@link Level#WARN} level when warnings are enabled.
*
* @param <T> {@link Class} type extension of {@link AbstractGemFireAsLastResourceAspectSupport}.
* @param message {@link String} containing the message to log.
* @param args array of {@link Object arguments} used to format the message.
* @return this aspect.
* @see org.slf4j.Logger#isWarnEnabled()
* @see org.slf4j.Logger#warn(String, Object...)
* @see #format(String, Object...)
*/
@SuppressWarnings("unchecked")
protected <T extends AbstractGemFireAsLastResourceAspectSupport> T logWarning(String message, Object... args) {
Logger logger = getLogger();
if (logger.isWarnEnabled()) {
logger.warn(format(message, args), args);
}
return (T) this;
}
/**
* Logs the given {@link String message} formatted with the given array of {@link Object arguments}
* at {@link Level#ERROR} level when error logging is enabled.
*
* @param <T> {@link Class} type extension of {@link AbstractGemFireAsLastResourceAspectSupport}.
* @param message {@link String} containing the message to log.
* @param args array of {@link Object arguments} used to format the message.
* @return this aspect.
* @see org.slf4j.Logger#isErrorEnabled()
* @see org.slf4j.Logger#error(String, Object...)
* @see #format(String, Object...)
*/
@SuppressWarnings("unchecked")
protected <T extends AbstractGemFireAsLastResourceAspectSupport> T logError(String message, Object... args) {
Logger logger = getLogger();
if (logger.isErrorEnabled()) {
logger.error(format(message, args), args);
}
return (T) this;
}
/**
* Constructs a new instance of the {@link InitialContext} configured with the given {@link Hashtable environment}.
*
* @param environment {@link Hashtable} containing environment configuration meta-data used to access
* the JNDI context in a managed environment.
* @return a new instance of the {@link InitialContext} configured with the given {@link Hashtable environment}.
* @throws NamingException if the {@link InitialContext} could not be initialized with
* the provided {@link Hashtable environment}.
* @see javax.naming.InitialContext
* @see java.util.Hashtable
*/
protected InitialContext newInitialContext(Hashtable<?, ?> environment) throws NamingException {
return new InitialContext(environment);
}
/**
* Constructs a new instance of {@link Logger} used by this application to log messages.
*
* @return a new, configured instance of {@link Logger}.
*/
protected Logger newLogger() {
return LoggerFactory.getLogger(getClass());
}
/**
* Resolves the {@link Context} used to perform lookups of registered, managed objects in a management environment.
*
* @return the {@link Context} used to perform lookups of registered, managed objects in a managed environment.
* @throws IllegalStateException if the {@link Context} could not be resolved.
* @see com.gemstone.gemfire.cache.GemFireCache#getJNDIContext()
* @see #newInitialContext(Hashtable)
* @see #resolveEnvironment()
* @see #resolveGemFireCache()
* @see #getContext()
* @see javax.naming.Context
*/
protected synchronized Context resolveContext() {
Context context = getContext();
if (context == null) {
Hashtable<?, ?> resolvedEnvironment = resolveEnvironment();
try {
context = this.context = newInitialContext(resolvedEnvironment);
}
catch (NamingException cause) {
GemFireCache gemfireCache = resolveGemFireCache();
context = this.context = (gemfireCache != null ? gemfireCache.getJNDIContext() : null);
if (context == null) {
throw new IllegalStateException(String.format(
"Failed to initialize an %1$s with the provided Environment configuration [%2$s]",
InitialContext.class.getName(), resolvedEnvironment), cause);
}
}
}
return context;
}
/**
* Resolves the {@link Hashtable environment} used by the application to configure the {@link InitialContext}.
*
* @return the resolved {@link Hashtable environment} used to configure the {@link InitialContext}.
* @see #getInitialContextFactory()
* @see #getProviderUrl()
* @see java.util.Hashtable
*/
protected Hashtable<?, ?> resolveEnvironment() {
Hashtable<Object, Object> environment = new Hashtable<Object, Object>();
environment.put(Context.INITIAL_CONTEXT_FACTORY, getInitialContextFactory());
environment.put(Context.PROVIDER_URL, getProviderUrl());
return environment;
}
/**
* Resolves a reference to the {@link GemFireCache} required by this Aspect to perform its function.
*
* This method either returns the configured {@link GemFireCache} instance or looks up
* the {@link GemFireCache} instance using GemFire's API.
*
* @return a reference to the resolved {@link GemFireCache} instance.
* @see org.springframework.data.gemfire.GemfireUtils#resolveGemFireCache()
* @see com.gemstone.gemfire.cache.GemFireCache
* @see #getGemFireCache()
*/
protected synchronized GemFireCache resolveGemFireCache() {
GemFireCache gemfireCache = getGemFireCache();
if (gemfireCache == null) {
gemfireCache = this.gemfireCache = GemfireUtils.resolveGemFireCache();
}
return gemfireCache;
}
/**
* Resolves the configured JNDI name used to lookup and resolve the GemFire JCA ResourceAdapter object
* from the JNDI context in the managed environment.
*
* @return a {@link String} containing the JNDI name used to lookup and resolve the GemFire JCA ResourceAdapter
* object from the JNDI context in the managed environment.
* @see #getGemFireJcaResourceAdapterJndiName()
*/
protected String resolveGemFireJcaResourceAdapterJndiName() {
String gemfireJcaResourceAdapterJndiName = getGemFireJcaResourceAdapterJndiName();
return (StringUtils.hasText(gemfireJcaResourceAdapterJndiName) ? gemfireJcaResourceAdapterJndiName
: DEFAULT_GEMFIRE_JCA_RESOURCE_ADAPTER_JNDI_NAME);
}
/**
* Builder method used to set the {@link #isThrowOnError() throwOnError} property.
*
* The {@link #isThrowOnError() throwOnError} property is used to indicate whether an Exception should be thrown
* when an error occurs during the normal operation and function of this Aspect. By default, the error condition
* is simply logged.
*
* @param <T> {@link Class sub-type} of this Aspect.
* @param throwOnError boolean value used to set the {@link #isThrowOnError() throwOnError} property.
* @return this Aspect.
* @see #isThrowOnError()
*/
@SuppressWarnings("unchecked")
public <T extends AbstractGemFireAsLastResourceAspectSupport> T withThrowOnError(boolean throwOnError) {
this.throwOnError = throwOnError;
return (T) this;
}
protected static class GemFireConnectionHolder {
private static final ThreadLocal<GFConnection> gemfireConnection = new ThreadLocal<GFConnection>();
public static GFConnection acquire(GFConnectionFactory connectionFactory) {
return acquire(connectionFactory, DEFAULT_THROW_ON_ERROR, NO_OP_LOGGER);
}
public static GFConnection acquire(GFConnectionFactory connectionFactory, boolean throwOnError,
Consumer<String> logger) {
try {
return of(connectionFactory.getConnection());
}
catch (ResourceException cause) {
String message =
String.format("Failed to acquire GemFire Connection from GemFire's JCA ResourceAdapter: %s",
cause.getMessage());
if (throwOnError) {
throw new RuntimeException(message, cause);
}
else {
logger.accept(message);
return null;
}
}
}
/* (non-Javadoc) */
public static GFConnection of(GFConnection connection) {
gemfireConnection.set(connection);
return connection;
}
/* (non-Javadoc) */
public static GFConnection get() {
return gemfireConnection.get();
}
public static void close() {
close(DEFAULT_THROW_ON_ERROR, NO_OP_LOGGER);
}
/* (non-Javadoc) */
public static void close(boolean throwOnError, Consumer<String> logger) {
GFConnection connection = get();
if (connection != null) {
try {
connection.close();
}
catch (ResourceException cause) {
String message = String.format("Failed to close GemFire Connection: %s", cause.getMessage());
if (throwOnError) {
throw new RuntimeException(message, cause);
}
else {
logger.accept(message);
}
}
};
}
}
/* (non-Javadoc) */
interface Consumer<T> {
void accept(T obj);
}
/* (non-Javadoc) */
interface Supplier<T> {
T get();
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation.support;
import javax.naming.NamingException;
import com.gemstone.gemfire.ra.GFConnectionFactory;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link GemFireAsLastResourceConnectionAcquiringAspect} class is a {@link AbstractGemFireAsLastResourceAspectSupport}
* implementation responsible for acquiring a GemFire Connection from GemFire's JCA ResourceAdapter,
* {@link GFConnectionFactory} after a CMT/JTA Transaction is began, which is initiated by
* Spring's Transaction infrastructure.
*
* @author John Blum
* @see org.aspectj.lang.annotation.Aspect
* @see org.aspectj.lang.annotation.Before
* @see org.springframework.data.gemfire.config.annotation.support.AbstractGemFireAsLastResourceAspectSupport
* @see com.gemstone.gemfire.ra.GFConnectionFactory
* @since 2.0.0
*/
@SuppressWarnings("unused")
@Aspect
public class GemFireAsLastResourceConnectionAcquiringAspect extends AbstractGemFireAsLastResourceAspectSupport {
private static final int DEFAULT_ORDER = 2048000;
@Autowired(required = false)
private GFConnectionFactory gemfireConnectionFactory;
/**
* Acquires (opens) a GemFire JCA ResourceAdapter Connection after the Spring CMT/JTA Transaction begins.
*/
@Before("atTransactionalType() || atTransactionalMethod()")
public void doGemFireConnectionFactoryGetConnection() {
logTraceInfo("Acquiring GemFire Connection from GemFire JCA ResourceAdapter registered at [%s]...",
resolveGemFireJcaResourceAdapterJndiName());
GemFireConnectionHolder.acquire(resolveGemFireConnectionFactory(), isThrowOnError(), new Consumer<String>() {
@Override
public void accept(String message) {
logError(message);
}
});
}
/* (non-Javadoc) */
synchronized GFConnectionFactory resolveGemFireConnectionFactory() {
GFConnectionFactory connectionFactory = getGemFireConnectionFactory();
if (connectionFactory == null) {
String resolvedGemFireJcaResourceAdapterJndiName = resolveGemFireJcaResourceAdapterJndiName();
try {
connectionFactory = this.gemfireConnectionFactory =
(GFConnectionFactory) resolveContext().lookup(resolvedGemFireJcaResourceAdapterJndiName);
}
catch (NamingException cause) {
throw new RuntimeException(String.format(
"Failed to resolve a GFConnectionFactory from the configured JNDI context name [%s]",
resolvedGemFireJcaResourceAdapterJndiName), cause);
}
}
return connectionFactory;
}
/**
* Returns the default order used by this AOP Aspect in the chain of Aspects configured
* in Spring's Transaction Management.
*
* @return an int value specifying the default order used by this AOP Aspect in the chain of Aspects
* configured in Spring's Transaction Management.
*/
@Override
protected Integer getDefaultOrder() {
return DEFAULT_ORDER;
}
/**
* Returns a reference to the configured {@link GFConnectionFactory} instance.
*
* @return a reference to the configured {@link GFConnectionFactory} instance; may be {@literal null}.
* @see com.gemstone.gemfire.ra.GFConnectionFactory
*/
public synchronized GFConnectionFactory getGemFireConnectionFactory() {
return this.gemfireConnectionFactory;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation.support;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
/**
* The {@link GemFireAsLastResourceConnectionClosingAspect} class is a {@link AbstractGemFireAsLastResourceAspectSupport}
* implementation responsible for closing the GemFire Connection obtained from the GemFire JCA ResourceAdapter
* deployed in a managed environment when using GemFire as the Last Resource in a CMT/JTA Transaction
* initiated from Spring's Transaction infrastructure.
*
* @author John Blum
* @see org.aspectj.lang.annotation.Aspect
* @see org.aspectj.lang.annotation.After
* @see org.springframework.data.gemfire.config.annotation.support.AbstractGemFireAsLastResourceAspectSupport
* @since 2.0.0
*/
@SuppressWarnings("unused")
@Aspect
public class GemFireAsLastResourceConnectionClosingAspect extends AbstractGemFireAsLastResourceAspectSupport {
private static final int DEFAULT_ORDER = 1024000;
/**
* Closes the GemFire JCA ResourceAdapter Connection after the Spring CMT/JTA Transaction completes.
*/
@After("atTransactionalType() || atTransactionalMethod()")
public void doGemFireConnectionClose() {
logTraceInfo("Closing GemFire Connection...");
GemFireConnectionHolder.close(isThrowOnError(), new Consumer<String>() {
@Override
public void accept(String message) {
logWarning(message);
}
});
}
/**
* Returns the default order used by this AOP Aspect in the chain of Aspects configured
* in Spring's Transaction Management.
*
* @return an int value specifying the default order used by this AOP Aspect in the chain of Aspects
* configured in Spring's Transaction Management.
*/
@Override
protected Integer getDefaultOrder() {
return DEFAULT_ORDER;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation.support;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The RegionDataAccessTracingAspect class...
*
* @author John Blum
* @since 1.0.0
*/
@Aspect
@SuppressWarnings("unused")
public class RegionDataAccessTracingAspect {
private final Logger logger = newLogger();
/* (non-Javadoc) */
protected Logger newLogger() {
return LoggerFactory.getLogger(getClass());
}
/* (non-Javadoc) */
protected String getCurrentThreadStackTrace() {
StringWriter writer = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(writer));
return writer.toString();
}
/* (non-Javadoc) */
protected Logger getLogger() {
return this.logger;
}
/* (non-Javadoc) */
@Pointcut("target(com.gemstone.gemfire.cache.Region)")
private void regionPointcut() {}
/* (non-Javadoc) */
@Pointcut("execution(* com.gemstone.gemfire.cache.Region.create(..))"
+ " || execution(* com.gemstone.gemfire.cache.Region.get(..))"
+ " || execution(* com.gemstone.gemfire.cache.Region.getAll(..))"
+ " || execution(* com.gemstone.gemfire.cache.Region.put(..))"
+ " || execution(* com.gemstone.gemfire.cache.Region.putAll(..))"
+ " || execution(* com.gemstone.gemfire.cache.Region.putIfAbsent(..))"
+ " || execution(* com.gemstone.gemfire.cache.Region.remove(..))"
+ " || execution(* com.gemstone.gemfire.cache.Region.replace(..))"
+ " || execution(* com.gemstone.gemfire.cache.Region.selectValue(..))"
+ " || execution(* com.gemstone.gemfire.cache.Region.values(..))"
+ "")
private void regionDataAccessPointcut() {}
/* (non-Javadoc) */
@Before("regionPointcut() && regionDataAccessPointcut()")
public void regionDataAccessTracingAdvice() {
getLogger().trace("Region data access call [{}]", getCurrentThreadStackTrace());
}
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.gemstone.gemfire.cache.GemFireCache;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.config.annotation.support.GemFireAsLastResourceConnectionAcquiringAspect;
import org.springframework.data.gemfire.config.annotation.support.GemFireAsLastResourceConnectionClosingAspect;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* Unit tests for {@link GemFireAsLastResourceConfiguration}.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.gemfire.config.annotation.GemFireAsLastResourceConfiguration
* @since 2.0.0
*/
@RunWith(MockitoJUnitRunner.class)
public class GemFireAsLastResourceConfigurationUnitTests {
@Mock
private AnnotationMetadata mockImportMetadata;
private GemFireAsLastResourceConfiguration configuration;
@Before
public void setup() {
configuration = new GemFireAsLastResourceConfiguration();
}
private AnnotationMetadata mockEnableTransactionManagementWithOrder(Integer order) {
Map<String, Object> enableTransactionManagementAttributes =
Collections.<String, Object>singletonMap("order", order);
when(mockImportMetadata.getAnnotationAttributes(eq(EnableTransactionManagement.class.getName())))
.thenReturn(enableTransactionManagementAttributes);
return mockImportMetadata;
}
@Test
public void resolveEnableTransactionManagementAttributes() {
Map<String, Object> enableTransactionManagementAttributesMap = new HashMap<String, Object>();
enableTransactionManagementAttributesMap.put("mode", AdviceMode.ASPECTJ);
enableTransactionManagementAttributesMap.put("order", 1);
enableTransactionManagementAttributesMap.put("proxyTargetClass", true);
when(mockImportMetadata.getAnnotationAttributes(eq(EnableTransactionManagement.class.getName())))
.thenReturn(enableTransactionManagementAttributesMap);
AnnotationAttributes enableTransactionManagementAttributes =
configuration.resolveEnableTransactionManagementAttributes(mockImportMetadata);
assertThat(enableTransactionManagementAttributes).isNotNull();
assertThat(enableTransactionManagementAttributes.<AdviceMode>getEnum("mode"))
.isEqualTo(AdviceMode.ASPECTJ);
assertThat(enableTransactionManagementAttributes.getBoolean("proxyTargetClass")).isTrue();
verify(mockImportMetadata, times(1))
.getAnnotationAttributes(eq(EnableTransactionManagement.class.getName()));
}
@Test(expected = IllegalStateException.class)
public void resolveEnableTransactionManagementAttributesThrowsIllegalStateException() {
try {
when(mockImportMetadata.getAnnotationAttributes(anyString())).thenReturn(null);
configuration.resolveEnableTransactionManagementAttributes(mockImportMetadata);
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage(String.format(
"The @%1$s annotation may only be used on a Spring application @%2$s class"
+ " that is also annotated with @%3$s having an explicit [order] set",
EnableGemFireAsLastResource.class.getSimpleName(), Configuration.class.getSimpleName(),
EnableTransactionManagement.class.getSimpleName()));
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void resolveEnableTransactionManagementOrderEqualsOne() {
assertThat(configuration.resolveEnableTransactionManagementOrder(
mockEnableTransactionManagementWithOrder(1))).isEqualTo(1);
}
@Test(expected = IllegalArgumentException.class)
public void resolveEnableTransactionManagementOrderThrowsIllegalArgumentExceptionForIntegerMaxValue() {
try {
configuration.resolveEnableTransactionManagementOrder(
mockEnableTransactionManagementWithOrder(Integer.MAX_VALUE));
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage(String.format(
"The @%1$s(order) attribute value [%2$d] must be explicitly set to a value other than"
+ " Integer.MAX_VALUE or Integer.MIN_VALUE",
EnableTransactionManagement.class.getSimpleName(), Integer.MAX_VALUE));
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test(expected = IllegalArgumentException.class)
public void resolveEnableTransactionManagementOrderThrowsIllegalArgumentExceptionForIntegerMinValue() {
try {
configuration.resolveEnableTransactionManagementOrder(
mockEnableTransactionManagementWithOrder(Integer.MIN_VALUE));
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage(String.format(
"The @%1$s(order) attribute value [%2$d] must be explicitly set to a value other than"
+ " Integer.MAX_VALUE or Integer.MIN_VALUE",
EnableTransactionManagement.class.getSimpleName(), Integer.MIN_VALUE));
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void getEnableTransactionManagementOrderReturnsValue() {
configuration.setImportMetadata(mockEnableTransactionManagementWithOrder(101));
assertThat(configuration.getEnableTransactionManagementOrder()).isEqualTo(101);
}
@Test(expected = IllegalStateException.class)
public void getEnableTransactionManagementOrderThrowsIllegalStateException() {
try {
configuration.getEnableTransactionManagementOrder();
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage(String.format("The @%1$s(order) attribute was not properly set [null];"
+ " Also, please make your Spring application @%2$s annotated class is annotated with both @%3$s and @%1$s",
EnableTransactionManagement.class.getSimpleName(), Configuration.class.getSimpleName(),
EnableGemFireAsLastResource.class.getSimpleName()));
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void gemfireCachePostProcessorSetsCopyOnReadToTrue() {
GemFireCache mockGemFireCache = mock(GemFireCache.class);
assertThat(configuration.gemfireCachePostProcessor(mockGemFireCache)).isNull();
verify(mockGemFireCache, times(1)).setCopyOnRead(eq(true));
}
@Test
public void gemfireCachePostProcessorHandlesNull() {
assertThat(configuration.gemfireCachePostProcessor(null)).isNull();
}
@Test
public void gemfireJcaConnectionAcquiringAspectIsOrderPlusOne() {
configuration.setImportMetadata(mockEnableTransactionManagementWithOrder(0));
GemFireAsLastResourceConnectionAcquiringAspect aspect = configuration.gemfireJcaConnectionAcquiringAspect();
assertThat(aspect).isNotNull();
assertThat(aspect.getOrder()).isEqualTo(1);
}
@Test
public void gemfireJcaConnectionClosingAspectIsOrderMinusOne() {
configuration.setImportMetadata(mockEnableTransactionManagementWithOrder(0));
GemFireAsLastResourceConnectionClosingAspect aspect = configuration.gemfireJcaConnectionClosingAspect();
assertThat(aspect).isNotNull();
assertThat(aspect.getOrder()).isEqualTo(-1);
}
}

View File

@@ -0,0 +1,449 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import com.gemstone.gemfire.cache.GemFireCache;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.internal.matchers.VarargMatcher;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.springframework.util.Assert;
/**
* Unit tests for {@link AbstractGemFireAsLastResourceAspectSupport}.
*
* @author John Blum
* @see javax.naming.Context
* @see javax.naming.InitialContext
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.Spy
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.gemfire.config.annotation.support.AbstractGemFireAsLastResourceAspectSupport
* @see com.gemstone.gemfire.cache.GemFireCache
* @since 2.0.0
*/
@RunWith(MockitoJUnitRunner.class)
public class AbstractGemFireAsLastResourceAspectSupportUnitTests {
@Mock
private Logger mockLogger;
@Spy
private AbstractGemFireAsLastResourceAspectSupport aspectSupport;
@Before
@SuppressWarnings("all")
public void setup() {
doReturn(mockLogger).when(aspectSupport).getLogger();
}
@Test
public void setAndGetOrder() {
assertThat(aspectSupport.getOrder()).isEqualTo(AbstractGemFireAsLastResourceAspectSupport.DEFAULT_ORDER);
aspectSupport.setOrder(1);
assertThat(aspectSupport.getOrder()).isEqualTo(1);
aspectSupport.setOrder(-2);
assertThat(aspectSupport.getOrder()).isEqualTo(-2);
aspectSupport.setOrder(Integer.MAX_VALUE);
assertThat(aspectSupport.getOrder()).isEqualTo(AbstractGemFireAsLastResourceAspectSupport.DEFAULT_ORDER);
aspectSupport.setOrder(Integer.MIN_VALUE);
assertThat(aspectSupport.getOrder()).isEqualTo(AbstractGemFireAsLastResourceAspectSupport.DEFAULT_ORDER);
}
@Test
public void logDebugInfoWhenDebuggingIsDisabled() {
when(mockLogger.isDebugEnabled()).thenReturn(false);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logDebugInfo("message", "arg"))
.isSameAs(aspectSupport);
verify(mockLogger, times(1)).isDebugEnabled();
verify(mockLogger, never()).debug(anyString());
verify(mockLogger, never()).debug(anyString(), any(Object.class));
verify(mockLogger, never()).debug(anyString(), any(Object.class), any(Object.class));
verify(mockLogger, never()).debug(anyString(), any(Object[].class));
}
@Test
public void logDebugInfoWhenDebuggingIsEnabled() {
when(mockLogger.isDebugEnabled()).thenReturn(true);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logDebugInfo("test %s message",
"debug", "test")) .isSameAs(aspectSupport);
verify(mockLogger, times(1)).isDebugEnabled();
verify(mockLogger, times(1)).debug(eq("test debug message"),
VariableArgumentMatcher.varArgThat("debug", "test"));
}
@Test
public void logInfoWhenInfoIsDisabled() {
when(mockLogger.isInfoEnabled()).thenReturn(false);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logInfo("message", "arg"))
.isSameAs(aspectSupport);
verify(mockLogger, times(1)).isInfoEnabled();
verify(mockLogger, never()).info(anyString(), any(Object.class));
verify(mockLogger, never()).info(anyString(), any(Object.class), any(Object.class));
verify(mockLogger, never()).info(anyString(), any(Object[].class));
}
@Test
public void logInfoWhenInfoIsEnabled() {
when(mockLogger.isInfoEnabled()).thenReturn(true);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logInfo("test %s message", "info"))
.isSameAs(aspectSupport);
verify(mockLogger, times(1)).isInfoEnabled();
verify(mockLogger, times(1)).info(eq("test info message"),
VariableArgumentMatcher.varArgThat("info"));
}
@Test
public void logTraceInfoWhenTracingIsDisabled() {
when(mockLogger.isTraceEnabled()).thenReturn(false);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logTraceInfo("message", "arg"))
.isSameAs(aspectSupport);
verify(mockLogger, times(1)).isTraceEnabled();
verify(mockLogger, never()).trace(anyString());
verify(mockLogger, never()).trace(anyString(), any(Object.class));
verify(mockLogger, never()).trace(anyString(), any(Object.class), any(Object.class));
verify(mockLogger, never()).trace(anyString(), any(Object[].class));
}
@Test
public void logTraceInfoWhenTracingIsEnabled() {
when(mockLogger.isTraceEnabled()).thenReturn(true);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logTraceInfo("test %s message", "trace"))
.isSameAs(aspectSupport);
verify(mockLogger, times(1)).isTraceEnabled();
verify(mockLogger, times(1)).trace(eq("test trace message"));
}
@Test
public void logTraceInfoUsingMessageSupplierWhenTracingIsEnabled() {
when(mockLogger.isTraceEnabled()).thenReturn(true);
assertThat(aspectSupport.logTraceInfo(
new AbstractGemFireAsLastResourceAspectSupport.Supplier<String>() {
@Override
public String get() {
return "trace test message with Supplier";
}
})).isSameAs(aspectSupport);
verify(mockLogger, times(1)).isTraceEnabled();
verify(mockLogger, times(1)).trace(eq("trace test message with Supplier"));
}
@Test
public void logWarningWhenWarningsAreDisabled() {
when(mockLogger.isWarnEnabled()).thenReturn(false);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logWarning("message", "arg"))
.isSameAs(aspectSupport);
verify(mockLogger, times(1)).isWarnEnabled();
verify(mockLogger, never()).warn(anyString());
verify(mockLogger, never()).warn(anyString(), any(Object.class));
verify(mockLogger, never()).warn(anyString(), any(Object.class), any(Object.class));
verify(mockLogger, never()).warn(anyString(), any(Object[].class));
}
@Test
public void logWarningWhenWarningsAreEnabled() {
when(mockLogger.isWarnEnabled()).thenReturn(true);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logWarning("test %s message", "warning"))
.isSameAs(aspectSupport);
verify(mockLogger, times(1)).isWarnEnabled();
verify(mockLogger, times(1)).warn(eq("test warning message"),
VariableArgumentMatcher.varArgThat("warning"));
}
@Test
public void logErrorWhenErrorsAreDisabled() {
when(mockLogger.isErrorEnabled()).thenReturn(false);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logError("message", "arg"))
.isSameAs(aspectSupport);
verify(mockLogger, times(1)).isErrorEnabled();
verify(mockLogger, never()).error(anyString());
verify(mockLogger, never()).error(anyString(), any(Object.class));
verify(mockLogger, never()).error(anyString(), any(Object.class), any(Object.class));
verify(mockLogger, never()).error(anyString(), any(Object[].class));
}
@Test
public void logErrorWhenErrorsAreEnabled() {
when(mockLogger.isErrorEnabled()).thenReturn(true);
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>logError("test %s message", "error"))
.isSameAs(aspectSupport);
verify(mockLogger, times(1)).isErrorEnabled();
verify(mockLogger, times(1)).error(eq("test error message"),
VariableArgumentMatcher.varArgThat("error"));
}
@Test
public void resolveContextReturnsConfiguredContext() throws NamingException {
Context mockContext = mock(Context.class);
doReturn(mockContext).when(aspectSupport).getContext();
assertThat(aspectSupport.resolveContext()).isSameAs(mockContext);
verify(aspectSupport, never()).newInitialContext(any(Hashtable.class));
verifyZeroInteractions(mockContext);
}
@Test
@SuppressWarnings("all")
public void resolveContextReturnsNewInitialContext() throws NamingException {
Hashtable<String, Object> mockEnvironment = new Hashtable<String, Object>();
InitialContext mockContext = mock(InitialContext.class);
doReturn(mockContext).when(aspectSupport).newInitialContext(any(Hashtable.class));
doReturn(mockEnvironment).when(aspectSupport).resolveEnvironment();
assertThat(aspectSupport.resolveContext()).isEqualTo(mockContext);
verify(aspectSupport, times(1)).getContext();
verify(aspectSupport, times(1)).newInitialContext(eq(mockEnvironment));
verifyZeroInteractions(mockContext);
}
@Test
public void resolveContextHandlesNamingExceptionAndReturnsGemFireJndiContext() throws NamingException {
Context mockContext = mock(Context.class);
GemFireCache mockGemFireCache = mock(GemFireCache.class);
Hashtable<String, Object> mockEnvironment = new Hashtable<String, Object>();
doThrow(new NamingException("TEST")).when(aspectSupport).newInitialContext(any(Hashtable.class));
doReturn(mockEnvironment).when(aspectSupport).resolveEnvironment();
doReturn(mockGemFireCache).when(aspectSupport).resolveGemFireCache();
doReturn(mockContext).when(mockGemFireCache).getJNDIContext();
assertThat(aspectSupport.resolveContext()).isEqualTo(mockContext);
verify(aspectSupport, times(1)).getContext();
verify(aspectSupport, times(1)).resolveEnvironment();
verify(aspectSupport, times(1)).newInitialContext(eq(mockEnvironment));
verify(aspectSupport, times(1)).resolveGemFireCache();
verify(mockGemFireCache, times(1)).getJNDIContext();
}
@Test(expected = IllegalStateException.class)
public void resolveContextHandlesNamingExceptionAndThrowsIllegalStateException() throws NamingException {
Hashtable<String, Object> testEnvironment = new Hashtable<String, Object>();
testEnvironment.put("key", "test");
doThrow(new NamingException("TEST")).when(aspectSupport).newInitialContext(any(Hashtable.class));
doReturn(testEnvironment).when(aspectSupport).resolveEnvironment();
doReturn(null).when(aspectSupport).resolveGemFireCache();
try {
aspectSupport.resolveContext();
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage(
"Failed to initialize an %1$s with the provided Environment configuration [%2$s]",
InitialContext.class.getName(), testEnvironment.toString());
assertThat(expected).hasCauseInstanceOf(NamingException.class);
assertThat(expected.getCause()).hasMessage("TEST");
assertThat(expected.getCause()).hasNoCause();
throw expected;
}
finally {
verify(aspectSupport, times(1)).getContext();
verify(aspectSupport, times(1)).resolveEnvironment();
verify(aspectSupport, times(1)).newInitialContext(eq(testEnvironment));
verify(aspectSupport, times(1)).resolveGemFireCache();
}
}
@Test
public void resolveEnvironmentContainsInitialContextFactoryAndProviderUrl() {
doReturn("org.example.test.naming.AppServerContextFactory").when(aspectSupport)
.getInitialContextFactory();
doReturn("jndi:rmi://java/comp:jndi/context").when(aspectSupport).getProviderUrl();
Hashtable<?, ?> resolvedEnvironment = aspectSupport.resolveEnvironment();
assertThat(resolvedEnvironment).isNotNull();
assertThat(resolvedEnvironment).hasSize(2);
assertThat(resolvedEnvironment.containsKey(Context.INITIAL_CONTEXT_FACTORY)).isTrue();
assertThat(resolvedEnvironment.get(Context.INITIAL_CONTEXT_FACTORY))
.isEqualTo("org.example.test.naming.AppServerContextFactory");
assertThat(resolvedEnvironment.containsKey(Context.PROVIDER_URL)).isTrue();
assertThat(resolvedEnvironment.get(Context.PROVIDER_URL)).isEqualTo("jndi:rmi://java/comp:jndi/context");
}
@Test
public void resolveGemFireCacheReturnsConfiguredGemFireCache() {
GemFireCache mockGemFireCache = mock(GemFireCache.class);
doReturn(mockGemFireCache).when(aspectSupport).getGemFireCache();
assertThat(aspectSupport.resolveGemFireCache()).isSameAs(mockGemFireCache);
verify(aspectSupport, times(1)).getGemFireCache();
}
@Test
public void resolveGemFireCacheReturnsNull() {
assertThat(aspectSupport.resolveGemFireCache()).isNull();
verify(aspectSupport, times(1)).getGemFireCache();
}
@Test
@SuppressWarnings("all")
public void resolveGemFireJcaResourceAdapterJndiNameReturnsConfiguredJndiName() {
doReturn("java/comp:gemfire/jca/resourceAdapter").when(aspectSupport)
.getGemFireJcaResourceAdapterJndiName();
assertThat(aspectSupport.resolveGemFireJcaResourceAdapterJndiName())
.isEqualTo("java/comp:gemfire/jca/resourceAdapter");
verify(aspectSupport, times(1)).getGemFireJcaResourceAdapterJndiName();
}
@Test
public void resolveGemFireJcaResourceAdapterJndiNameReturnsDefaultJndiName() {
assertThat(aspectSupport.getGemFireJcaResourceAdapterJndiName()).isNull();
assertThat(aspectSupport.resolveGemFireJcaResourceAdapterJndiName())
.isEqualTo(AbstractGemFireAsLastResourceAspectSupport.DEFAULT_GEMFIRE_JCA_RESOURCE_ADAPTER_JNDI_NAME);
}
@Test
public void withThrowOnErrorAndIsThrowOnError() {
assertThat(aspectSupport.isThrowOnError()).isFalse();
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>withThrowOnError(true)).isSameAs(aspectSupport);
assertThat(aspectSupport.isThrowOnError()).isTrue();
assertThat(aspectSupport.<AbstractGemFireAsLastResourceAspectSupport>withThrowOnError(false)).isSameAs(aspectSupport);
assertThat(aspectSupport.isThrowOnError()).isFalse();
}
// TODO refactor this BS; damn you Mockito for your inability to match Varargs completely/reliably; WTF!
protected static final class VariableArgumentMatcher<T> extends ArgumentMatcher<T> implements VarargMatcher {
protected static Object[] varArgThat(Object... expectedArguments) {
return argThat(new VariableArgumentMatcher<Object[]>(expectedArguments));
}
private Object[] expectedArguments;
@SuppressWarnings("unchecked")
protected VariableArgumentMatcher(Object... expectedArguments) {
Assert.notNull(expectedArguments, "Expected arguments must not be null");
this.expectedArguments = expectedArguments;
}
@Override
@SuppressWarnings("unchecked")
public boolean matches(Object actualArgument) {
return asList(this.expectedArguments).containsAll(asList(actualArgument));
}
private List<Object> asList(Object argument) {
return Arrays.asList(toArray(argument));
}
private Object[] toArray(Object argument) {
return (argument instanceof Object[] ? (Object[]) argument : new Object[] { argument });
}
}
}

View File

@@ -0,0 +1,390 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.resource.ResourceException;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.ra.GFConnection;
import com.gemstone.gemfire.ra.GFConnectionFactory;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.gemfire.config.annotation.EnableGemFireAsLastResource;
import org.springframework.data.gemfire.config.annotation.GemFireAsLastResourceConfiguration;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
/**
* Integration tests for {@link EnableGemFireAsLastResource}.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see com.gemstone.gemfire.ra.GFConnection
* @see com.gemstone.gemfire.ra.GFConnectionFactory
* @see org.springframework.data.gemfire.config.annotation.EnableGemFireAsLastResource
* @see org.springframework.data.gemfire.config.annotation.GemFireAsLastResourceConfiguration
* @see org.springframework.transaction.PlatformTransactionManager
* @see org.springframework.transaction.annotation.EnableTransactionManagement
* @see org.springframework.transaction.annotation.Transactional
* @since 1.9.0
*/
public class EnableGemFireAsLastResourceIntegrationTests {
private static List<SpringGemFireTransactionEvents> transactionEvents =
new ArrayList<SpringGemFireTransactionEvents>();
@Before
public void setup() {
transactionEvents.clear();
}
private ConfigurableApplicationContext newApplicationContext(Class<?>... annotatedClasses) {
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses);
applicationContext.registerShutdownHook();
return applicationContext;
}
@Test
public void configurationIsCorrect() {
ConfigurableApplicationContext applicationContext =
newApplicationContext(TestGemFireAsLastResourceConfiguration.class);
assertThat(applicationContext).isNotNull();
GemFireCache gemfireCache = applicationContext.getBean("gemfireCache", GemFireCache.class);
assertThat(gemfireCache).isNotNull();
assertThat(gemfireCache.getCopyOnRead()).isTrue();
GemFireAsLastResourceConnectionAcquiringAspect connectionAcquiringAspect =
applicationContext.getBean(GemFireAsLastResourceConnectionAcquiringAspect.class);
assertThat(connectionAcquiringAspect).isNotNull();
assertThat(connectionAcquiringAspect.getOrder()).isEqualTo(3);
GemFireAsLastResourceConnectionClosingAspect connectionClosingAspect =
applicationContext.getBean(GemFireAsLastResourceConnectionClosingAspect.class);
assertThat(connectionClosingAspect).isNotNull();
assertThat(connectionClosingAspect.getOrder()).isEqualTo(1);
}
private void transactionEventsForTransactionalServiceAreCorrect(
Class<? extends TestTransactionalService> transactionalServiceType) {
ConfigurableApplicationContext applicationContext =
newApplicationContext(TestSpringApplicationConfiguration.class);
assertThat(applicationContext).isNotNull();
assertThat(applicationContext.containsBean(transactionalServiceType.getSimpleName())).isTrue();
assertThat(transactionEvents).isEmpty();
GFConnectionFactory gemfireConnectionFactory = applicationContext.getBean(GFConnectionFactory.class);
assertThat(gemfireConnectionFactory).isNotNull();
GemFireAsLastResourceConnectionAcquiringAspect connectionAcquiringAspect =
applicationContext.getBean(GemFireAsLastResourceConnectionAcquiringAspect.class);
assertThat(connectionAcquiringAspect).isNotNull();
assertThat(connectionAcquiringAspect.getGemFireConnectionFactory()).isSameAs(gemfireConnectionFactory);
TestTransactionalService service =
applicationContext.getBean(transactionalServiceType.getSimpleName(), transactionalServiceType);
assertThat(service).isNotNull();
service.doInTransactionCommits();
assertThat(transactionEvents).containsExactly(SpringGemFireTransactionEvents.BEGIN,
SpringGemFireTransactionEvents.GET_CONNECTION,
SpringGemFireTransactionEvents.TRANSACTION,
SpringGemFireTransactionEvents.COMMIT,
SpringGemFireTransactionEvents.CLOSE_CONNECTION);
transactionEvents.clear();
assertThat(transactionEvents).isEmpty();
try {
service.doInTransactionRollsback();
}
catch (RuntimeException expected) {
assertThat(expected).hasMessage("TEST");
assertThat(expected).hasNoCause();
throw expected;
}
finally {
assertThat(transactionEvents).containsExactly(SpringGemFireTransactionEvents.BEGIN,
SpringGemFireTransactionEvents.GET_CONNECTION,
SpringGemFireTransactionEvents.TRANSACTION,
SpringGemFireTransactionEvents.ROLLBACK,
SpringGemFireTransactionEvents.CLOSE_CONNECTION);
}
}
@Test(expected = RuntimeException.class)
public void transactionEventsForTransactionalServiceClassAreCorrect() {
transactionEventsForTransactionalServiceAreCorrect(TestTransactionalServiceClass.class);
}
@Test(expected = RuntimeException.class)
public void transactionEventsForTransactionalServiceMethodsAreCorrect() {
transactionEventsForTransactionalServiceAreCorrect(TestTransactionalServiceMethods.class);
}
@Test(expected = IllegalStateException.class)
public void missingEnableTransactionManagerAnnotationThrowsIllegalStateException() {
try {
newApplicationContext(TestMissingEnableTransactionManagementAnnotationConfiguration.class);
}
catch (BeanCreationException expected) {
assertThat(expected).hasMessageStartingWith(String.format("Error creating bean with name '%s'",
GemFireAsLastResourceConfiguration.class.getName()));
assertThat(expected).hasCauseInstanceOf(IllegalStateException.class);
assertThat(expected.getCause()).hasMessage("The @EnableGemFireAsLastResource annotation may only be used"
+ " on a Spring application @Configuration class that is also annotated with"
+ " @EnableTransactionManagement having an explicit [order] set");
assertThat(expected.getCause()).hasNoCause();
throw (IllegalStateException) expected.getCause();
}
}
@Test(expected = IllegalArgumentException.class)
public void missingEnableTransactionManagementOrderAttributeConfigurationThrowsIllegalArgumentException() {
try {
newApplicationContext(TestMissingEnableTransactionManagementOrderAttributeConfiguration.class);
}
catch (BeanCreationException expected) {
assertThat(expected).hasMessageStartingWith(String.format("Error creating bean with name '%s'",
GemFireAsLastResourceConfiguration.class.getName()));
assertThat(expected).hasCauseInstanceOf(IllegalArgumentException.class);
assertThat(expected.getCause()).hasMessage("The @EnableTransactionManagement(order) attribute value"
+ " [2147483647] must be explicitly set to a value other than Integer.MAX_VALUE or Integer.MIN_VALUE");
assertThat(expected.getCause()).hasNoCause();
throw (IllegalArgumentException) expected.getCause();
}
}
@Configuration
@EnableGemFireAsLastResource
@EnableTransactionManagement(order = 2)
static class TestGemFireAsLastResourceConfiguration {
@Bean("gemfireCache")
GemFireCache mockGemFireCache() {
final AtomicBoolean copyOnRead = new AtomicBoolean(false);
GemFireCache mockGemFireCache = mock(GemFireCache.class);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
copyOnRead.set(invocation.getArgumentAt(0, Boolean.class));
return null;
}
}).when(mockGemFireCache).setCopyOnRead(anyBoolean());
when(mockGemFireCache.getCopyOnRead()).thenAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable {
return copyOnRead.get();
}
});
return mockGemFireCache;
}
@Bean
GFConnectionFactory mockGemFireConnectionFactory() throws ResourceException {
GFConnectionFactory mockGemFireConnectionFactory = mock(GFConnectionFactory.class);
final GFConnection mockGemFireConnection = mock(GFConnection.class);
when(mockGemFireConnectionFactory.getConnection()).thenAnswer(new Answer<GFConnection>() {
@Override
public GFConnection answer(InvocationOnMock invocationOnMock) throws Throwable {
transactionEvents.add(SpringGemFireTransactionEvents.GET_CONNECTION);
return mockGemFireConnection;
}
});
doAnswer(new Answer<Void>() {
@Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
transactionEvents.add(SpringGemFireTransactionEvents.CLOSE_CONNECTION);
return null;
}
}).when(mockGemFireConnection).close();
return mockGemFireConnectionFactory;
}
@Bean("transactionManager")
PlatformTransactionManager mockTransactionManager() {
PlatformTransactionManager mockTransactionManager = mock(PlatformTransactionManager.class);
when(mockTransactionManager.getTransaction(any(TransactionDefinition.class))).thenAnswer(
new Answer<TransactionStatus>() {
@Override
public TransactionStatus answer(InvocationOnMock invocationOnMock) throws Throwable {
TransactionStatus mockTransactionStatus = mock(TransactionStatus.class);
transactionEvents.add(SpringGemFireTransactionEvents.BEGIN);
return mockTransactionStatus;
}
});
doAnswer(new Answer<Void>() {
@Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
transactionEvents.add(SpringGemFireTransactionEvents.COMMIT);
return null;
}
}).when(mockTransactionManager).commit(any(TransactionStatus.class));
doAnswer(new Answer<Void>() {
@Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
transactionEvents.add(SpringGemFireTransactionEvents.ROLLBACK);
return null;
}
}).when(mockTransactionManager).rollback(any(TransactionStatus.class));
return mockTransactionManager;
}
}
@Configuration
@EnableGemFireAsLastResource
static class TestMissingEnableTransactionManagementAnnotationConfiguration {
}
@Configuration
@EnableGemFireAsLastResource
@EnableTransactionManagement
static class TestMissingEnableTransactionManagementOrderAttributeConfiguration {
@Bean("transactionManager")
PlatformTransactionManager mockTransactionManager() {
return mock(PlatformTransactionManager.class);
}
}
@Configuration
@Import(TestGemFireAsLastResourceConfiguration.class)
static class TestSpringApplicationConfiguration {
@Bean("TestTransactionalServiceClass")
TestTransactionalServiceClass testTransactionalServiceClass() {
return new TestTransactionalServiceClass();
}
@Bean("TestTransactionalServiceMethods")
TestTransactionalServiceMethods testTransactionalServiceMethods() {
return new TestTransactionalServiceMethods();
}
}
enum SpringGemFireTransactionEvents {
BEGIN,
CLOSE_CONNECTION,
COMMIT,
GET_CONNECTION,
ROLLBACK,
TRANSACTION,
}
interface TestTransactionalService {
void doInTransactionCommits();
void doInTransactionRollsback();
}
@Service("TestTransactionalServiceClass")
@Transactional
static class TestTransactionalServiceClass implements TestTransactionalService {
public void doInTransactionCommits() {
transactionEvents.add(SpringGemFireTransactionEvents.TRANSACTION);
}
public void doInTransactionRollsback() {
transactionEvents.add(SpringGemFireTransactionEvents.TRANSACTION);
throw new RuntimeException("TEST");
}
}
@Service("TestTransactionalServiceMethods")
static class TestTransactionalServiceMethods implements TestTransactionalService {
@Transactional
public void doInTransactionCommits() {
transactionEvents.add(SpringGemFireTransactionEvents.TRANSACTION);
}
@Transactional
public void doInTransactionRollsback() {
transactionEvents.add(SpringGemFireTransactionEvents.TRANSACTION);
throw new RuntimeException("TEST");
}
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.resource.ResourceException;
import com.gemstone.gemfire.ra.GFConnection;
import com.gemstone.gemfire.ra.GFConnectionFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;
/**
* Unit tests for {@link GemFireAsLastResourceConnectionAcquiringAspect}.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.Spy
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.gemfire.config.annotation.support.GemFireAsLastResourceConnectionAcquiringAspect
* @since 2.0.0
*/
@RunWith(MockitoJUnitRunner.class)
public class GemFireAsLastResourceConnectionAcquiringAspectUnitTests {
@Mock
private Context mockContext;
@Mock
private GFConnection mockGemFireConnection;
@Mock
private GFConnectionFactory mockGemFireConnectionFactory;
@Mock
private Logger mockLogger;
private GemFireAsLastResourceConnectionAcquiringAspect aspect;
@Before
public void setup() {
aspect = spy(new GemFireAsLastResourceConnectionAcquiringAspect());
when(aspect.getLogger()).thenReturn(mockLogger);
}
@Test
public void connectionAcquiringAspectHasLowerPriorityThanConnectionClosingAspect() {
assertThat(aspect.getOrder()).isGreaterThan(new GemFireAsLastResourceConnectionClosingAspect().getOrder());
}
@Test
public void doConnectionFactoryGetConnectionReturnsConnection() throws ResourceException {
when(mockLogger.isTraceEnabled()).thenReturn(true);
doReturn(mockGemFireConnectionFactory).when(aspect).resolveGemFireConnectionFactory();
when(mockGemFireConnectionFactory.getConnection()).thenReturn(mockGemFireConnection);
aspect.doGemFireConnectionFactoryGetConnection();
assertThat(AbstractGemFireAsLastResourceAspectSupport.GemFireConnectionHolder.get())
.isEqualTo(mockGemFireConnection);
verify(aspect, times(1)).resolveGemFireConnectionFactory();
verify(aspect, times(1)).resolveGemFireJcaResourceAdapterJndiName();
verify(mockGemFireConnectionFactory, times(1)).getConnection();
verify(mockLogger, times(1))
.trace(eq(String.format("Acquiring GemFire Connection from GemFire JCA ResourceAdapter registered at [%s]...",
GemFireAsLastResourceConnectionAcquiringAspect.DEFAULT_GEMFIRE_JCA_RESOURCE_ADAPTER_JNDI_NAME)));
}
@Test
public void resolveGemFireConnectionFactoryFromAutowiring() {
when(aspect.getGemFireConnectionFactory()).thenReturn(mockGemFireConnectionFactory);
assertThat(aspect.resolveGemFireConnectionFactory()).isSameAs(mockGemFireConnectionFactory);
verify(aspect, times(1)).getGemFireConnectionFactory();
verify(aspect, never()).resolveGemFireJcaResourceAdapterJndiName();
verify(aspect, never()).resolveContext();
}
@Test
public void resolveGemFireConnectionFactoryFromJndiContext() throws NamingException {
when(aspect.getGemFireConnectionFactory()).thenReturn(null);
doReturn(mockContext).when(aspect).resolveContext();
when(mockContext.lookup(anyString())).thenReturn(mockGemFireConnectionFactory);
assertThat(aspect.resolveGemFireConnectionFactory()).isEqualTo(mockGemFireConnectionFactory);
verify(aspect, times(1)).getGemFireConnectionFactory();
verify(aspect, times(1)).resolveGemFireJcaResourceAdapterJndiName();
verify(aspect, times(1)).resolveContext();
verify(mockContext, times(1))
.lookup(eq(GemFireAsLastResourceConnectionAcquiringAspect.DEFAULT_GEMFIRE_JCA_RESOURCE_ADAPTER_JNDI_NAME));
}
@Test(expected = RuntimeException.class)
public void resolveGemFireConnectionFactoryFromJndiContextThrowsNamingException() throws NamingException {
when(aspect.getGemFireConnectionFactory()).thenReturn(null);
when(aspect.getGemFireJcaResourceAdapterJndiName()).thenReturn("java:comp/gemfire/jca");
doReturn(mockContext).when(aspect).resolveContext();
when(mockContext.lookup(anyString())).thenThrow(new NamingException("TEST"));
try {
aspect.resolveGemFireConnectionFactory();
}
catch (RuntimeException expected) {
assertThat(expected).hasMessage(
"Failed to resolve a GFConnectionFactory from the configured JNDI context name [java:comp/gemfire/jca]");
assertThat(expected).hasCauseInstanceOf(NamingException.class);
assertThat(expected.getCause()).hasMessage("TEST");
assertThat(expected.getCause()).hasNoCause();
throw expected;
}
finally {
verify(aspect, times(1)).getGemFireConnectionFactory();
verify(aspect, times(1)).resolveGemFireJcaResourceAdapterJndiName();
verify(aspect, times(1)).resolveContext();
verify(mockContext, times(1)).lookup(eq("java:comp/gemfire/jca"));
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.config.annotation.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import javax.resource.ResourceException;
import com.gemstone.gemfire.ra.GFConnection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;
/**
* Unit tests for {@link GemFireAsLastResourceConnectionClosingAspect}.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.Spy
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.gemfire.config.annotation.support.GemFireAsLastResourceConnectionClosingAspect
* @since 2.0.0
*/
@RunWith(MockitoJUnitRunner.class)
public class GemFireAsLastResourceConnectionClosingAspectUnitTests {
private GemFireAsLastResourceConnectionClosingAspect aspect;
@Mock
private GFConnection mockGemFireConnection;
@Mock
private Logger mockLogger;
@Before
public void setup() {
aspect = spy(new GemFireAsLastResourceConnectionClosingAspect());
when(aspect.getLogger()).thenReturn(mockLogger);
}
@Test
public void connectionClosingAspectHasHigherPriorityThanConnectionAcquiringAspect() {
assertThat(aspect.getOrder()).isLessThan(new GemFireAsLastResourceConnectionAcquiringAspect().getOrder());
}
@Test
public void doGemFireConnectionCloseIsSuccessful() throws ResourceException {
AbstractGemFireAsLastResourceAspectSupport.GemFireConnectionHolder.of(mockGemFireConnection);
when(mockLogger.isTraceEnabled()).thenReturn(true);
aspect.doGemFireConnectionClose();;
verify(mockGemFireConnection, times(1)).close();
verify(mockLogger, times(1)).trace(eq("Closing GemFire Connection..."));
}
}

View File

@@ -3,7 +3,9 @@ Bundle-Name: Spring Data GemFire
Bundle-SymbolicName: org.springframework.data.gemfire
Bundle-Vendor: Pivotal Software, Inc.
Import-Package: sun.reflect;version="0";resolution:=optional
Import-Template: javax.enterprise.*;version="[1.0,2.0)";resolution:=optional,
Import-Template: javax.naming.*;version="[1.0,2.0)";resolution:=optional,
javax.enterprise.*;version="[1.0,2.0)";resolution:=optional,
javax.resource.*;version="[1.0,2.0)";resolution:=optional,
org.apache.commons.logging.*;version="[1.1.1, 2.0.0)",
org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional,
org.aspectj.*;version="[1.8.2, 2.0.0)";resolution:=optional,