DATAGEODE-16 - Add support for Geode JCA ResourceAdapter.
This commit is contained in:
@@ -65,15 +65,16 @@ or `<gfe:client-cache>` declaration, which acts as an exception translator and i
|
||||
the _Spring_ infrastructure and used accordingly.
|
||||
|
||||
[[apis:transaction-management]]
|
||||
== Transaction Management
|
||||
== Local, Cache Transaction Management
|
||||
|
||||
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].
|
||||
|
||||
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 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).
|
||||
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 Apache Geode, _Spring Data Geode_ provides a dedicated, per-cache, `PlatformTransactionManager` that,
|
||||
once declared, allows Region operations to be executed atomically through _Spring_:
|
||||
@@ -83,21 +84,230 @@ once declared, allows Region operations to be executed atomically through _Sprin
|
||||
<gfe:transaction-manager id="txManager" cache-ref="myCache"/>
|
||||
----
|
||||
|
||||
NOTE: The example above can be simplified even further by eliminating the `cache-ref` attribute if the GemFire cache
|
||||
NOTE: The example above can be simplified even further by eliminating the `cache-ref` attribute if the Geode cache
|
||||
is defined under the default name, `gemfireCache`. As with the other _Spring Data Geode_ 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.
|
||||
Additionally, the transaction manager name is "`gemfireTransactionManager`" if not explicitly specified.
|
||||
|
||||
Currently, Apache Geode 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,
|
||||
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 bevior of the underlying Geode transaction manager, please refer to the Geode
|
||||
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 Apache Geode 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), Apache Geode is *not*
|
||||
an XA compliant resource. Therefore, Apache Geode 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 Apache Geode 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 Apache Geode 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. Apache Geode) in such an arrangement.
|
||||
|
||||
1) First, you must complete Steps 1-4 in Geode's documentation
|
||||
http://geode.apache.org/docs/guide/11/developing/transactions/JTA_transactions.html#task_sln_x3b_wk[here].
|
||||
|
||||
NOTE: #1 above is independent of your _Spring [Boot] and/or [Data Geode]_ application
|
||||
and must be completed successfully.
|
||||
|
||||
2) Referring to Step 5 in Geode's http://geode.apache.org/docs/guide/11/developing/transactions/JTA_transactions.html#task_sln_x3b_wk[documentation],
|
||||
_Spring Data Geode's_ Annotation support will attempt to set the `GemFireCache`, http://geode.apache.org/releases/latest/javadoc/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 Geode's http://geode.apache.org/docs/guide/11/developing/transactions/JTA_transactions.html#task_sln_x3b_wk[documentation]
|
||||
and let _Spring Data Geode_ work its magic. All you need do is annotate your _Spring_ `@Configuration` class
|
||||
with _Spring Data Geode'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 Geode'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 Geode'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 Geode's_ `@EnableGemFireAsLastResource` annotation imports configuration containing 2 Aspect
|
||||
bean definitions that handles the Geode `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
|
||||
+ Geode API yourself, as shown in the
|
||||
Geode http://geode.apache.org/docs/guide/11/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 Geode'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 Apache Geode in JTA transactions, see http://geode.apache.org/docs/guide/11/developing/transactions/JTA_transactions.html[here].
|
||||
|
||||
For more details on configuring Apache Geode as a "_Last Resource_",
|
||||
see http://geode.apache.org/docs/guide/11/developing/transactions/JTA_transactions.html#concept_csy_vfb_wk[here].
|
||||
|
||||
include::{basedocdir}/reference/cq-container.adoc[]
|
||||
|
||||
[[apis:declarable]]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.config.annotation;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.geode.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;
|
||||
|
||||
/**
|
||||
* 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.apache.geode.cache.GemFireCache
|
||||
* @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
|
||||
* @since 1.0.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 Optional.ofNullable(order)
|
||||
.filter(it -> !(it == Integer.MAX_VALUE || it == Integer.MIN_VALUE))
|
||||
.orElseThrow(() -> newIllegalArgumentException(
|
||||
"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)));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("all")
|
||||
protected AnnotationAttributes resolveEnableTransactionManagementAttributes(
|
||||
AnnotationMetadata importMetadata) {
|
||||
|
||||
Map<String, Object> enableTransactionManagementAttributes =
|
||||
importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName());
|
||||
|
||||
return Optional.ofNullable(enableTransactionManagementAttributes)
|
||||
.map(AnnotationAttributes::fromMap)
|
||||
.orElseThrow(() -> newIllegalStateException(
|
||||
"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()));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected Integer getEnableTransactionManagementOrder() {
|
||||
|
||||
return Optional.ofNullable(this.enableTransactionManagementOrder)
|
||||
.orElseThrow(() -> newIllegalStateException(
|
||||
"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(), String.valueOf(this.enableTransactionManagementOrder),
|
||||
Configuration.class.getSimpleName(), EnableGemFireAsLastResource.class.getSimpleName()));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@Bean
|
||||
public Object gemfireCachePostProcessor(@Autowired(required = false) GemFireCache gemfireCache) {
|
||||
Optional.ofNullable(gemfireCache).ifPresent(cache -> cache.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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
/*
|
||||
* 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.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
import javax.resource.ResourceException;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.ra.GFConnection;
|
||||
import org.apache.geode.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 org.apache.geode.cache.GemFireCache
|
||||
* @see org.apache.geode.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 = message -> {};
|
||||
|
||||
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 org.apache.geode.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() {
|
||||
|
||||
return Optional.of(this.order)
|
||||
.filter(order -> !(order == Integer.MAX_VALUE || order == Integer.MIN_VALUE))
|
||||
.orElseGet(this::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 java.util.function.Supplier
|
||||
* @see #logTraceInfo(Supplier)
|
||||
*/
|
||||
protected <T extends AbstractGemFireAsLastResourceAspectSupport> T logTraceInfo(String message, Object... args) {
|
||||
return logTraceInfo(() -> 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 java.util.function.Supplier
|
||||
* @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 org.apache.geode.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) {
|
||||
context = this.context = Optional.ofNullable(resolveGemFireCache()).map(GemFireCache::getJNDIContext)
|
||||
.orElseThrow(() -> newIllegalStateException(cause,
|
||||
"Failed to initialize an %1$s with the provided Environment configuration ['%2$s']",
|
||||
InitialContext.class.getName(), resolvedEnvironment));
|
||||
}
|
||||
}
|
||||
|
||||
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<>();
|
||||
|
||||
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 org.apache.geode.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<>();
|
||||
|
||||
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 newRuntimeException(cause, message);
|
||||
}
|
||||
else {
|
||||
logger.accept(message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static GFConnection of(GFConnection connection) {
|
||||
gemfireConnection.set(connection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static Optional<GFConnection> get() {
|
||||
return Optional.ofNullable(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) {
|
||||
|
||||
get().ifPresent(connection -> {
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (ResourceException cause) {
|
||||
|
||||
String message = String.format("Failed to close GemFire Connection: %s", cause.getMessage());
|
||||
|
||||
if (throwOnError) {
|
||||
throw newRuntimeException(cause, message);
|
||||
}
|
||||
else {
|
||||
logger.accept(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
|
||||
import org.apache.geode.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.apache.geode.ra.GFConnectionFactory
|
||||
* @see org.aspectj.lang.annotation.Aspect
|
||||
* @see org.aspectj.lang.annotation.Before
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractGemFireAsLastResourceAspectSupport
|
||||
* @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(), this::logError);
|
||||
}
|
||||
|
||||
/* (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 newRuntimeException(cause,
|
||||
"Failed to resolve a GFConnectionFactory from the configured JNDI context name [%s]",
|
||||
resolvedGemFireJcaResourceAdapterJndiName);
|
||||
}
|
||||
}
|
||||
|
||||
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 org.apache.geode.ra.GFConnectionFactory
|
||||
*/
|
||||
public synchronized GFConnectionFactory getGemFireConnectionFactory() {
|
||||
return this.gemfireConnectionFactory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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(), this::logWarning);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.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 org.apache.geode.cache.GemFireCache;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.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.junit.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.singletonMap("order", order);
|
||||
|
||||
when(mockImportMetadata.getAnnotationAttributes(eq(EnableTransactionManagement.class.getName())))
|
||||
.thenReturn(enableTransactionManagementAttributes);
|
||||
|
||||
return mockImportMetadata;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveEnableTransactionManagementAttributes() {
|
||||
|
||||
Map<String, Object> enableTransactionManagementAttributesMap = new HashMap<>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.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 static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
|
||||
import org.apache.geode.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.junit.MockitoJUnitRunner;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* 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.junit.MockitoJUnitRunner
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractGemFireAsLastResourceAspectSupport
|
||||
* @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();
|
||||
// TODO why the f#&k does this not work Mockito?!
|
||||
//verify(mockLogger, times(1))
|
||||
// .debug(eq("test debug message"), eq("debug"), eq("test"));
|
||||
// TODO this ridiculous sh!t works
|
||||
//verify(mockLogger, times(1)).debug(eq("test debug message"),
|
||||
// ArgumentMatchers.<Object[]>any());
|
||||
// TODO and so does this, but what a hack!
|
||||
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.<AbstractGemFireAsLastResourceAspectSupport>logTraceInfo(
|
||||
() -> "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<>();
|
||||
|
||||
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<>();
|
||||
|
||||
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<>();
|
||||
|
||||
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> implements ArgumentMatcher<T>, VarargMatcher {
|
||||
|
||||
protected static Object[] varArgThat(Object... expectedArguments) {
|
||||
return argThat(new VariableArgumentMatcher<>(expectedArguments));
|
||||
}
|
||||
|
||||
private Object[] expectedArguments;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected VariableArgumentMatcher(Object... expectedArguments) {
|
||||
this.expectedArguments = Optional.ofNullable(expectedArguments)
|
||||
.orElseThrow(() -> newIllegalArgumentException("Expected arguments must not be null"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean matches(T 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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* 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.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.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 org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.ra.GFConnection;
|
||||
import org.apache.geode.ra.GFConnectionFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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 org.apache.geode.ra.GFConnection
|
||||
* @see org.apache.geode.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 2.0.0
|
||||
*/
|
||||
public class EnableGemFireAsLastResourceIntegrationTests {
|
||||
|
||||
private static List<SpringGemFireTransactionEvents> transactionEvents = new ArrayList<>();
|
||||
|
||||
@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() {
|
||||
|
||||
AtomicBoolean copyOnRead = new AtomicBoolean(false);
|
||||
|
||||
GemFireCache mockGemFireCache = mock(GemFireCache.class);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
copyOnRead.set(invocation.getArgument(0));
|
||||
return null;
|
||||
}).when(mockGemFireCache).setCopyOnRead(anyBoolean());
|
||||
|
||||
when(mockGemFireCache.getCopyOnRead()).thenAnswer(invocation -> copyOnRead.get());
|
||||
|
||||
return mockGemFireCache;
|
||||
}
|
||||
|
||||
@Bean
|
||||
GFConnectionFactory mockGemFireConnectionFactory() throws ResourceException {
|
||||
|
||||
GFConnectionFactory mockGemFireConnectionFactory = mock(GFConnectionFactory.class);
|
||||
|
||||
GFConnection mockGemFireConnection = mock(GFConnection.class);
|
||||
|
||||
when(mockGemFireConnectionFactory.getConnection()).thenAnswer(invocation -> {
|
||||
transactionEvents.add(SpringGemFireTransactionEvents.GET_CONNECTION);
|
||||
return mockGemFireConnection;
|
||||
});
|
||||
|
||||
doAnswer(invocation -> transactionEvents.add(SpringGemFireTransactionEvents.CLOSE_CONNECTION))
|
||||
.when(mockGemFireConnection).close();
|
||||
|
||||
return mockGemFireConnectionFactory;
|
||||
}
|
||||
|
||||
@Bean("transactionManager")
|
||||
PlatformTransactionManager mockTransactionManager() {
|
||||
|
||||
PlatformTransactionManager mockTransactionManager = mock(PlatformTransactionManager.class);
|
||||
|
||||
when(mockTransactionManager.getTransaction(any(TransactionDefinition.class))).thenAnswer(invocation -> {
|
||||
|
||||
TransactionStatus mockTransactionStatus = mock(TransactionStatus.class);
|
||||
|
||||
transactionEvents.add(SpringGemFireTransactionEvents.BEGIN);
|
||||
|
||||
return mockTransactionStatus;
|
||||
});
|
||||
|
||||
doAnswer(invocation -> transactionEvents.add(SpringGemFireTransactionEvents.COMMIT))
|
||||
.when(mockTransactionManager).commit(any(TransactionStatus.class));
|
||||
|
||||
doAnswer(invocation -> transactionEvents.add(SpringGemFireTransactionEvents.ROLLBACK))
|
||||
.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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.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 org.apache.geode.ra.GFConnection;
|
||||
import org.apache.geode.ra.GFConnectionFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.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.junit.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().orElse(null))
|
||||
.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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.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 org.apache.geode.ra.GFConnection;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.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.junit.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..."));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user