How to fix RemoteException in server regarding ehcache cache replication? (Tomcat 8.5.84 | Grails 2.4.5 | grails-cache 1.1.8 | grails-cache-ehcache - grails

I have a Grails 2.4.5 application deployed on a server with Tomcat 8.5.84 and JDK_1.8.0_341.
I have an issue with the cache replication from one server (node 2) to the other (node 1).
When a new deployment is done, sometimes the cache replicates without any issues automatically, and the caches are also well synchronized after each deployment if somehow caches happen to not be synchronized at some point.
I'm looking for reliable cache replication, which will help with reliable data and avoid inconsistent data after each refresh because one node might not have all the data or only some of the data.
Here is the common error I keep seeing in the server logs below. I'm hoping to at least understand what is triggering that error and how to fix it. I'm mostly interested in the root cause.
The original cache configuration worked well with Grails 2.2.0, Tomcat 7.0.109 and jdk1.7.0_331.
2023-02-13 13:17:50,287 [http-nio-16280-exec-4] INFO filters.LoggingFilters - [EXIT] - controller: chat, action: createMessage
2023-02-13 13:17:50,549 [Replication Thread] DEBUG distribution.RMICacheManagerPeerProvider - Lookup URL //cacheBaseUrl:cachePort/Chat(cache name)
2023-02-13 13:17:50,552 [Replication Thread] DEBUG distribution.RMICacheManagerPeerProvider - Lookup URL //cacheBaseUrl:cachePort/Chat.channel.chat.general(cache name)
2023-02-13 13:17:50,555 [Replication Thread] WARN distribution.RMIAsynchronousCacheReplicator - Unable to send message to remote peer. Message was: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.io.InvalidClassException: net.sf.ehcache.distribution.EventMessage; local class incompatible: stream classdesc serialVersionUID = -293616939110963630, local class serialVersionUID = -293616939110963629
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.io.InvalidClassException: net.sf.ehcache.distribution.EventMessage; local class incompatible: stream classdesc serialVersionUID = -293616939110963630, local class serialVersionUID = -293616939110963629
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:389)
at sun.rmi.transport.Transport$2.run(Transport.java:202)
at sun.rmi.transport.Transport$2.run(Transport.java:199)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:198)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:572)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:836)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.access$400(TCPTransport.java:624)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$1.run(TCPTransport.java:689)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$1.run(TCPTransport.java:686)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:686)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:622)
at java.lang.Thread.run(Thread.java:745)
at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:303)
at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:279)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:163)
at net.sf.ehcache.distribution.RMICachePeer_Stub.send(Unknown Source)
at net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator.writeReplicationQueue(RMIAsynchronousCacheReplicator.java:314)
at net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator.replicationThreadMain(RMIAsynchronousCacheReplicator.java:127)
at net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator.access$000(RMIAsynchronousCacheReplicator.java:58)
at net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator$ReplicationThread.run(RMIAsynchronousCacheReplicator.java:389)
Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.io.InvalidClassException: net.sf.ehcache.distribution.EventMessage; local class incompatible: stream classdesc serialVersionUID = -293616939110963630, local class serialVersionUID = -293616939110963629
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:348)
at sun.rmi.transport.Transport$2.run(Transport.java:202)
at sun.rmi.transport.Transport$2.run(Transport.java:199)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:198)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:572)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:836)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.access$400(TCPTransport.java:624)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$1.run(TCPTransport.java:689)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$1.run(TCPTransport.java:686)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:686)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:622)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.InvalidClassException: net.sf.ehcache.distribution.EventMessage; local class incompatible: stream classdesc serialVersionUID = -293616939110963630, local class serialVersionUID = -293616939110963629
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:694)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2006)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1853)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2186)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1670)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:504)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:462)
at java.util.ArrayList.readObject(ArrayList.java:780)
at sun.reflect.GeneratedMethodAccessor141.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1186)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2322)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2213)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1670)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:504)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:462)
at sun.rmi.server.UnicastRef.unmarshalValue(UnicastRef.java:325)
at sun.rmi.server.UnicastServerRef.unmarshalParametersUnchecked(UnicastServerRef.java:629)
at sun.rmi.server.UnicastServerRef.unmarshalParameters(UnicastServerRef.java:615)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:338)
... 14 more
2023-02-13 13:17:50,556 [Replication Thread] WARN distribution.RMIAsynchronousCacheReplicator - Unable to send message to remote peer. Message was: error marshalling arguments; nested exception is:
java.net.SocketException: Broken pipe (Write failed)
java.rmi.MarshalException: error marshalling arguments; nested exception is:
java.net.SocketException: Broken pipe (Write failed)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:159)
at net.sf.ehcache.distribution.RMICachePeer_Stub.send(Unknown Source)
at net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator.writeReplicationQueue(RMIAsynchronousCacheReplicator.java:314)
at net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator.replicationThreadMain(RMIAsynchronousCacheReplicator.java:127)
at net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator.access$000(RMIAsynchronousCacheReplicator.java:58)
at net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator$ReplicationThread.run(RMIAsynchronousCacheReplicator.java:389)
Caused by: java.net.SocketException: Broken pipe (Write failed)
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
at java.net.SocketOutputStream.write(SocketOutputStream.java:155)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.write(BufferedOutputStream.java:126)
at java.io.ObjectOutputStream$BlockDataOutputStream.drain(ObjectOutputStream.java:1877)
at java.io.ObjectOutputStream$BlockDataOutputStream.writeByte(ObjectOutputStream.java:1915)
at java.io.ObjectOutputStream.writeFatalException(ObjectOutputStream.java:1576)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:351)
at sun.rmi.server.UnicastRef.marshalValue(UnicastRef.java:292)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:154)
... 5 more
It seems from the error that a somehow different class net.sf.ehcache.distribution.EventMessage is being used at some point, creating a class cast exception, but I'm not sure what causes that and why. I'll need to find out more details about how Grails uses that class underneath.
The class that seems to cause the exception:
Class net.sf.ehcache.distribution.EventMessage
package net.sf.ehcache.distribution;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import java.io.Serializable;
/**
* An Event Message, in respect of a particular cache.
* <p/>
* The message is Serializable, so that it can be sent across the network.
* <p/>
* The value of an Element is referenced with a SoftReference, so that a
* value will fail to be delivered in preference to an OutOfMemory error.
*
* #author Greg Luck
* #version $Id: EventMessage.java 5192 2012-01-18 17:39:08Z cdennis $
*/
public class EventMessage implements Serializable {
private static final long serialVersionUID = -293616939110963630L;
/**
* Enumeration of event types.
*/
public enum EventType {
/**
* A put or update event.
*/
PUT,
/**
* A remove or invalidate event.
*/
REMOVE,
/**
* A removeAll, which removes all elements from a cache
*/
REMOVE_ALL;
}
/**
* The event component.
*/
private final EventType type;
/**
* The element component.
*/
private final Element element;
/**
* The key component.
*/
private final Serializable key;
/**
* The associated cache.
*/
private final transient Ehcache cache;
/**
* Full constructor.
*
* #param cache
* #param type
* #param key
* #param element
*/
public EventMessage(Ehcache cache, EventType type, Serializable key, Element element) {
this.cache = cache;
this.type = type;
this.key = key;
this.element = element;
}
/**
* Gets the associated {#code Ehcache}.
*
* #return the associated cache
*/
public final Ehcache getEhcache() {
return cache;
}
/**
* Gets the event.
*
* #return either {#link #PUT} or {#link #REMOVE}
*/
public final EventType getType() {
return type;
}
/**
* #return the element component of the message. null if a {#link #REMOVE} event
*/
public final Element getElement() {
return element;
}
/**
* #return the key component of the message. null if a {#link #PUT} event
*/
public final Serializable getSerializableKey() {
return key;
}
}

Related

Custom RabbitMessageListner Throws Wrapped Exception

I have custom MessageListener from the custom lib
import org.springframework.amqp.core.*
public class Subscriber {
private SimpleMessageListenerContainer simpleMessageListenerContainer;
private ConnectionFactory rabbitConnectionFactory;
public final class ScruberMessageListner implements MessageListener {
#Override
public void onMessage(Message message) {
// TODO Auto-generated method stub
}
}
}
When I create a Subscriber, the custom lib will care of starting the Container and managing listener and many more added functionality.
#Configuration
public class BrokerSubscriberConfiguration {
#Bean
public Subscriber treatmentEventSubscriber(EventConsumer treatmentEventConsumer) {
return Subscriber.createInitialisedSubscriber(treatmentEventConsumer, configurations,
null, true);
}
}
#SpringBootApplication
#ComponentScan(basePackages = "com.fin.bc")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Application loads the bring up message listener but it throws exception upon receiving the message.
LogMessage :
SimpleConnection#2a76b80a [delegate=amqp://admin#10.15.190.17:5672/hydra.services, localPort= 50786], acknowledgeMode=AUTO local queue size=0
2020-05-08 15:22:16,251 DEBUG org.springframework.amqp.rabbit.listener.BlockingQueueConsumer [SimpleAsyncTaskExecutor-1] Received message: (Body:'[B#4037c15c(byte[4])' MessageProperties [headers={}, contentLength=0, receivedDeliveryMode=NON_PERSISTENT, redelivered=false, receivedExchange=, receivedRoutingKey=Hydra.clone.q2, deliveryTag=1, consumerTag=amq.ctag-50OU2ydRYIghmrPKncVtUw, consumerQueue=Hydra.clone.q2])
2020-05-08 15:22:16,369 WARN org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler [SimpleAsyncTaskExecutor-1] Execution of Rabbit message listener failed.
org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener threw exception
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.wrapToListenerExecutionFailedExceptionIfNeeded(AbstractMessageListenerContainer.java:1651)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1603)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1489)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1466)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1461)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1410)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:870)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:854)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:78)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1137)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1043)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalArgumentException: Detected a Non-hex character at 1 or 2 position
at org.springframework.security.crypto.codec.Hex.decode(Hex.java:62)
at org.springframework.security.crypto.encrypt.HexEncodingTextEncryptor.decrypt(HexEncodingTextEncryptor.java:40)
at com.discover.cpp.mba.util.Encryptor.decrypt(Encryptor.java:32)
at com.discover.cpp.mba.Subscriber$SubscriberMessageListener.onMessage(Subscriber.java:521)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1600)
... 10 common frames omitted
2020-05-08 15:22:16,372 DEBUG org.springframework.amqp.rabbit.listener.ContainerUtils [SimpleAsyncTaskExecutor-1] Rejecting messages (requeue=true)
Does Rabbit Listener use decoding technique while processing message ?
This has nothing to do with the Spring AMQP framework. It's your code that is (indirectly) throwing the exception.
at org.springframework.security.crypto.encrypt.HexEncodingTextEncryptor.decrypt(HexEncodingTextEncryptor.java:40)
at com.discover.cpp.mba.util.Encryptor.decrypt(Encryptor.java:32)
at com.discover.cpp.mba.Subscriber$SubscriberMessageListener.onMessage(Subscriber.java:521)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1600)
It looks like you are calling some spring security code from your listener.
It appears the inbound message does not have the expected format.
I am guessing Hex.decode is expecting a character [a-f,0-9] and the data contains something else.

Typo3 Call to a member function on null

I have seen similar problems on Stackoverflow but none of those answers has worked for me (including clearing cache, clearing PHP opcode cache systems, de-activating and re-activating the extension). Hopefully someone can point me in the right direction.
I am running a scheduled command for an extension. At some point my command will need to call the method test() from the MyController class.
I have tried to create a reference to the class via an inheritance call AND by ALL injection methods but no matter which way I try it I always get the same issue...:
Call to a member function test() on null
Most recently I used the injection method that is not recommended, but it simplifies my example below so I'll use it for now. (VendorName and ExtensionName are obvs dummy names):
* #var \VendorName\ExtensionName\Controller\MyController
* #inject
*/
protected $mc;
public function myCommand()
{
return $this->mc->test(); //should return true
}
...and inside MyController
public function test()
{
return true;
}
The issue isn't the injection call on the command Class, but some automatically generated code on the MyController Class. It seems Extension Builder can cause the error by incorrectly creating the #inject line in the wrong place. Here is the code it created automatically:
/**
* #var \VendorName\ExtensionKey\Domain\Repository\ExampleRepository
* #inject
*/
protected $importService = null;
/**
* #inject
*/
protected $exampleRepository = null;
...that second #inject call creates the error. It should be just:
/**
* #var \VendorName\ExtensionKey\Domain\Repository\ExampleRepository
* #inject
*/
protected $importService = null;
protected $exampleRepository = null;
Unfortunately the dubugging doesn't tell you which class is causing the issue so I naturally thought it was my own code.

How to write an integration test for #RabbitListener annotation?

My question is really a follow up question to
RabbitMQ Integration Test and Threading
There it states to wrap "your listeners" and pass in a CountDownLatch and eventually all the threads will merge. This answer works if we were manually creating and injecting the message listener but for #RabbitListener annotations... i'm not sure how to pass in a CountDownLatch. The framework is auto magically creating the message listener behind the scenes.
Are there any other approaches?
With the help of #Gary Russell I was able to get an answer and used the following solution.
Conclusion: I must admit i'm indifferent about this solution (feels like a hack) but this is the only thing I could get to work and once you get over the initial one time setup and actually understand the 'work flow' it is not so painful. Basically comes down to defining ( 2 ) #Beans and adding them to your Integration Test config.
Example solution posted below with explanations. Please feel free to suggest improvements to this solution.
1. Define a ProxyListenerBPP that during spring initialization will listen for a specified clazz (i.e our test class that contains #RabbitListener) and
inject our custom CountDownLatchListenerInterceptor advice defined in the next step.
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
/**
* Implements BeanPostProcessor bean... during spring initialization we will
* listen for a specified clazz
* (i.e our #RabbitListener annotated class) and
* inject our custom CountDownLatchListenerInterceptor advice
* #author sjacobs
*
*/
public class ProxyListenerBPP implements BeanPostProcessor, BeanFactoryAware, Ordered, PriorityOrdered{
private BeanFactory beanFactory;
private Class<?> clazz;
public static final String ADVICE_BEAN_NAME = "wasCalled";
public ProxyListenerBPP(Class<?> clazz) {
this.clazz = clazz;
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (clazz.isAssignableFrom(bean.getClass())) {
ProxyFactoryBean pfb = new ProxyFactoryBean();
pfb.setProxyTargetClass(true); // CGLIB, false for JDK proxy (interface needed)
pfb.setTarget(bean);
pfb.addAdvice(this.beanFactory.getBean(ADVICE_BEAN_NAME, Advice.class));
return pfb.getObject();
}
else {
return bean;
}
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1000; // Just before #RabbitListener post processor
}
2. Create the MethodInterceptor advice impl that will hold the reference to the CountDownLatch. The CountDownLatch needs to be referenced in both in the Integration test thread and inside the async worker thread in the #RabbitListener. So we can later release back to the Integration Test thread as soon as the #RabbitListener async thread has completed execution. No need for polling.
import java.util.concurrent.CountDownLatch;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* AOP MethodInterceptor that maps a <b>Single</b> CountDownLatch to one method and invokes
* CountDownLatch.countDown() after the method has completed execution. The motivation behind this
* is for integration testing purposes of Spring RabbitMq Async Worker threads to be able to merge
* the Integration Test thread after an Async 'worker' thread completed its task.
* #author sjacobs
*
*/
public class CountDownLatchListenerInterceptor implements MethodInterceptor {
private CountDownLatch countDownLatch = new CountDownLatch(1);
private final String methodNameToInvokeCDL ;
public CountDownLatchListenerInterceptor(String methodName) {
this.methodNameToInvokeCDL = methodName;
}
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
if (this.methodNameToInvokeCDL.equals(methodName) ) {
//invoke async work
Object result = invocation.proceed();
//returns us back to the 'awaiting' thread inside the integration test
this.countDownLatch.countDown();
//"reset" CountDownLatch for next #Test (if testing for more async worker)
this.countDownLatch = new CountDownLatch(1);
return result;
} else
return invocation.proceed();
}
public CountDownLatch getCountDownLatch() {
return countDownLatch;
}
}
3. Next add to your Integration Test Config the following #Bean(s)
public class SomeClassThatHasRabbitListenerAnnotationsITConfig extends BaseIntegrationTestConfig {
// pass into the constructor the test Clazz that contains the #RabbitListener annotation into the constructor
#Bean
public static ProxyListenerBPP listenerProxier() { // note static
return new ProxyListenerBPP(SomeClassThatHasRabbitListenerAnnotations.class);
}
// pass the method name that will be invoked by the async thread in SomeClassThatHasRabbitListenerAnnotations.Class
// I.E the method name annotated with #RabbitListener or #RabbitHandler
// in our example 'listen' is the method name inside SomeClassThatHasRabbitListenerAnnotations.Class
#Bean(name=ProxyListenerBPP.ADVICE_BEAN_NAME)
public static Advice wasCalled() {
String methodName = "listen";
return new CountDownLatchListenerInterceptor( methodName );
}
// this is the #RabbitListener bean we are testing
#Bean
public SomeClassThatHasRabbitListenerAnnotations rabbitListener() {
return new SomeClassThatHasRabbitListenerAnnotations();
}
}
4. Finally, in the integration #Test call... after sending a message via rabbitTemplate to trigger the async thread... now call the CountDownLatch#await(...) method obtained from the interceptor and make sure to pass in a TimeUnit args so it can timeout in case of long running process or something goes wrong. Once the async the Integration Test thread is notified (awakened) and now we can finally begin to actually test/validate/verify the results of the async work.
#ContextConfiguration(classes={ SomeClassThatHasRabbitListenerAnnotationsITConfig.class } )
public class SomeClassThatHasRabbitListenerAnnotationsIT extends BaseIntegrationTest{
#Inject
private CountDownLatchListenerInterceptor interceptor;
#Inject
private RabbitTemplate rabbitTemplate;
#Test
public void shouldReturnBackAfterAsyncThreadIsFinished() throws Exception {
MyObject payload = new MyObject();
rabbitTemplate.convertAndSend("some.defined.work.queue", payload);
CountDownLatch cdl = interceptor.getCountDownLatch();
// wait for async thread to finish
cdl.await(10, TimeUnit.SECONDS); // IMPORTANT: set timeout args.
//Begin the actual testing of the results of the async work
// check the database?
// download a msg from another queue?
// verify email was sent...
// etc...
}
It's a bit more tricky with #RabbitListener but the simplest way is to advise the listener.
With the custom listener container factory just have your test case add the advice to the factory.
The advice would be a MethodInterceptor; the invocation will have 2 arguments; the channel and the (unconverted) Message. The advice has to be injected before the container(s) are created.
Alternatively, get a reference to the container using the registry and add the advice later (but you'll have to call initialize() to force the new advice to be applied).
An alternative would be a simple BeanPostProcessor to proxy your listener class before it is injected into the container. That way, you will see the method argumen(s) after any conversion; you will also be able to verify any result returned by the listener (for request/reply scenarios).
If you are not familiar with these techniques, I can try to find some time to spin up a quick example for you.
EDIT
I issued a pull request to add an example to EnableRabbitIntegrationTests. This adds a listener bean with 2 annotated listener methods, a BeanPostProcessor that proxies the listener bean before it is injected into a listener container. An Advice is added to the proxy which counts latches down when the expected messages are received.

Circular reference trying to configure custom permission evaluator in spring security

I'm trying to configure a custom permission evaluator but whenever the app starts up it's complaining of a circular reference.
The configuration code is:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
private static Logger logger = LogManager.getLogger(MethodSecurityConfig.class.getName());
#Autowired
DataSource dataSource;
#Autowired
Environment env;
#Autowired
AuthenticationManager auth;
public MethodSecurityConfig() {
logger.debug("Loading method security config.");
}
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return auth;
}
/**
* Override to set up the custom expression handler.
* #return The custom expression handler
*/
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return expressionHandler();
}
/**
* Defines a custom permission evaluator to evaluate the access permissions for Spring security.
* #return The default expression handler configured with a custom permission evaluator.
*/
#Bean
public DefaultMethodSecurityExpressionHandler expressionHandler(){
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setPermissionEvaluator(permissionEvaluator());
return handler;
}
/**
* Custom implementation of a permission evaluator
* #return An instance of {#link BasePermissionEvaluator}
*/
#Bean
public PermissionEvaluator permissionEvaluator() {
return new BasePermissionEvaluator();
}
}
When the app start up this exception occurs:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'methodSecurityConfig': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration.setPermissionEvaluator(java.util.List); nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'permissionEvaluator': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334) ~[AutowiredAnnotationBeanPostProcessor.class:4.2.1.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) ~[AbstractAutowireCapableBeanFactory.class:4.2.1.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) ~[AbstractAutowireCapableBeanFactory.class:4.2.1.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[AbstractAutowireCapableBeanFactory.class:4.2.1.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:305) ~[AbstractBeanFactory$1.class:4.2.1.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[DefaultSingletonBeanRegistry.class:4.2.1.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:301) ~[AbstractBeanFactory.class:4.2.1.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196) ~[AbstractBeanFactory.class:4.2.1.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:368) ~[ConstructorResolver.class:4.2.1.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(A
What am I doing wrong here? I tried various things like adding the AuthenticationManager. Is there something else I need to override?
I had faced the same issue. In my case the reason was there was another security configuration class which has #EnableGlobalMethodSecurity annotation. Removing that annotation solved the issue.
The reason to have two security configuration classes is described here.
To my understanding this happens when Spring first processes the other configuration class, because of #EnableGlobalMethodSecurity permissionEvaluator and similar beans are registered that time and later when this configuration class is processed we got this error.
Also looks like this behaviour is arbitrary, depends on which configuration class is first processed based on classpath ordering.

Jenkins HelloWorld plugin does not persist config after restart

I have been trying to create my first jenkins plugin. Everything is great except that the global config does not persist after the jenkins service is restarted.
THe config saves fine as long as the service is not restarted.
The global config jelly file...
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
Jenkins uses a set of tag libraries to provide uniformity in forms.
To determine where this tag is defined, first check the namespace URI,
and then look under $JENKINS/views/. For example, <f:section> is defined
in $JENKINS/views/lib/form/section.jelly.
It's also often useful to just check other similar scripts to see what
tags they use. Views are always organized according to its owner class,
so it should be straightforward to find them.
-->
<f:section title="Hello World Builder">
<f:entry title="French" field="useFrench"
description="Check if we should say hello in French">
<f:checkbox />
</f:entry>
</f:section>
</j:jelly>
After save jenkins is constructing a config file named
examplePlugin.examplePlugin.HelloWorldBuilder.xml
With Content
false
The descriptor itself is the following.
// Overridden for better type safety.
// If your plugin doesn't really define any property on Descriptor,
// you don't have to do this.
#Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl)super.getDescriptor();
}
/**
* Descriptor for {#link HelloWorldBuilder}. Used as a singleton.
* The class is marked as public so that it can be accessed from views.
*
* <p>
* See <tt>src/main/resources/hudson/plugins/hello_world/HelloWorldBuilder/*.jelly</tt>
* for the actual HTML fragment for the configuration screen.
*/
#Extension // This indicates to Jenkins that this is an implementation of an extension point.
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
/**
* To persist global configuration information,
* simply store it in a field and call save().
*
* <p>
* If you don't want fields to be persisted, use <tt>transient</tt>.
*/
private boolean useFrench;
/**
* Performs on-the-fly validation of the form field 'name'.
*
* #param value
* This parameter receives the value that the user has typed.
* #return
* Indicates the outcome of the validation. This is sent to the browser.
*/
public FormValidation doCheckName(#QueryParameter String value)
throws IOException, ServletException {
if (value.length() == 0)
return FormValidation.error("Please set a name");
if (value.length() < 4)
return FormValidation.warning("Isn't the name too short?");
return FormValidation.ok();
}
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
// Indicates that this builder can be used with all kinds of project types
return true;
}
/**
* This human readable name is used in the configuration screen.
*/
public String getDisplayName() {
return "Say hello world";
}
#Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
// To persist global configuration information,
// set that to properties and call save().
useFrench = formData.getBoolean("useFrench");
// ^Can also use req.bindJSON(this, formData);
// (easier when there are many fields; need set* methods for this, like setUseFrench)
save();
return super.configure(req,formData);
}
/**
* This method returns true if the global configuration says we should speak French.
*
* The method name is bit awkward because global.jelly calls this method to determine
* the initial state of the checkbox by the naming convention.
*/
public boolean getUseFrench() {
return useFrench;
}
}
Any help with why this is not reloading on reboot would be very helpful, since this seems to be a problem with the example project created by the maven archetype.
So this is problem with the hello world application. You need to define in your constructor that you want to load the configuration.
public DescriptorImpl(){
load();
}
That fixes the issue I was seeing with the configuration not being persisted.

Resources