Hi I am trying to add Spring IntegrationFlow but dont know what is the error for following scenario.
My IntegrationConfig is as below
#Configuration
#EnableIntegration
#IntegrationComponentScan
public class IntegrationConfig {
#Bean
public IntegrationFlow sayHelloFlow(){
String uri = "http://localhost:8081/hellos";
return IntegrationFlows.from("integration.example.gateway.channel")
.filter("headers['operation'] == 'OPERATION_A'")
.<SearchRequest>handle((request) -> {
Map<String, String> header = new HashMap<String, String>();
header.put("a_header", request.getHeaders().get("initial_val", String.class));
SearchRequestB obj = new SearchRequestB(
request.getPayload()+"Modified",
header);
})
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST))
.get();
}
}
My IntegrationGateway class is
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.MessagingGateway;
#MessagingGateway
public interface IntegrationGateway {
#Gateway(requestChannel = "integration.example.gateway.channel")
public String canSearch(String message);
}
In the above problem is once I remove handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST)) line it works properly. and by keeping same line I am getting following error
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.integration.dsl.IntegrationFlow]: Factory method 'sayHelloFlow' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: The 'currentComponent' (com.integration.config.IntegrationConfig$$Lambda$793/0x0000000800567440#340cb97f) is a one-way 'MessageHandler' and it isn't appropriate to configure 'outputChannel'. This is the end of the integration flow.
I am wanted to call the another REST end point within this handle method.
What is wrong I am doing here
Thanks in advance !!
Your problem that handle(Message<?>) is really a one-way endpoint with a void return type and can be used only in the end of flow. That’s what that error about : since this endpoint cannot produce reply, there is no way to call the next endpoint in the flow. It is more suspicious that your code introduces that obj variable and does nothing with it.
See more in docs : https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-class-cast.
To fix your solution we need to know if you want to call a rest with that obj or in parallel with this custom lambda.
Related
There's a piece of code that throws a exception:
java.lang.RuntimeException: cn.dev33.satoken.exception.NotLoginException: Invalid Token:ldxutBDDKBEDa9LjWNTKLFbW7g7B86qU.
And then it goes into handleRuntimeException rather than returnNotLoginException method.
#Component
#Slf4j
#Primary
public class MyLockKeyBuilder extends DefaultLockKeyBuilder {
#Override
public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
String key = super.buildKey(invocation, definitionKeys);
Object loginId = StpUtil.getLoginId(); // throw a exception
key = loginId.toString();
return key;
}
}
#ControllerAdvice(basePackages = "com.test")
#Slf4j
public class GraceExceptionHandlerApp {
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(value = NotLoginException.class)
#ResponseBody
public JSONObject returnNotLoginException(NotLoginException e) {
e.printStackTrace();
String message = e.getMessage();
ResponseStatusEnum failed = ResponseStatusEnum.UNAUTHORIZED;
failed.setMsg(message);
return ZheliResult.exception(failed);
}
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(RuntimeException.class)
#ResponseBody
public JSONObject handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
return ZheliResult.errorCustom(ResponseStatusEnum.FAILED);
}
...
}
I want it goes into the returnNotLoginException method, could anyone tell me how to do it?
UPDATE
I've made a mistake, really, for I didn't offer enough info.
Missed Info:
My application was a distributed system and services to invoke another via rpc communication. MyLockKeyBuilder was on the provider service, and GraceExceptionHandlerApp was on the comsumer service.
When the provider service throw a exception and before it being passed to the comsumer sevice, it would be filter by a Filter called ExceptionFilter, which wrap the exception that the comsumer side doesn't recognize to RuntimeException, to avoid serialization issue.
Finally I solved this problem by rewritting the ExceptionFilter class to allow original NotLoginException to be passed to the consumer side.
NotLoginException is the inner exception of your RuntimeException. If you want your controller advice to handle it, catch the RuntimeException buildKey and throw its inner exception.
Based on your question,
java.lang.RuntimeException: cn.dev33.satoken.exception.NotLoginException: Invalid Token:
Your exception type is java.lang.RuntimeException & cause of exception is NotLoginException.
Controller advice will invoke respective method when type of exception matches & not cause of exception.
So if you really want to invoke returnNotLoginException, then you need to throw NotLoginException in your logic instead of throwing RuntimeException.
Something like:
..
throw new NotLoginException("exception"); //in your StpUtil.getLoginId();
..
I am using SpringBoot version 1.5.9.
I can’t understand why my Fallback class doesn’t work out.
Maybe I'm doing something wrong?
My Feign client:
#FeignClient(
name = "prices",
url = "${prices.url}",
configuration = MyFeignConfig.class,
fallbackFactory = FallbackClass.class
)
public interface PricesFeignClient {
#GetMapping("/{userId}")
PriceModel get(
#PathVariable("userId") String userId
);
}
Here is the fallback class:
#Component
public class FallbackClass implements FallbackFactory<PricesFeignClient> {
#Override
public PricesFeignClient create(Throwable cause) {
return new PricesFeignClient() {
#Override
public PriceModel get(String userId) {
System.out.println("LALALA");
return null;
}
};
}
}
In theory, my fallback method should work out if my Feign client returns an error.
Here in the Feign client in the files in prices.url I specified the wrong URL (simulated the situation that my remote service to which I am making a call is unavailable). Knowing my Feign client should return with an error and the Fallback class should be called in which in the console I should receive the message: "LALALA".
This message is not in the console: my Fallback class is not being called. Instead, I get an error stating that the requested resource was not found.
Please tell me what could be the problem? Can I make a mistake somewhere?
The thing is that now I'm trying to get my Fallback class to work. And then I want to call another Fagnet class in the Fallback class with a different URL so that it works out if my main service is unavailable.
Tell me, please. thanks
I had to add to dependencies this for it to work (also don't forget to insert feign.hystrix.enabled: true as was said in the comments)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
If I just have a Hystrix Command defined as class, i have control over defining the group key and command key like below.
private static class MyHystrixCommand extends HystrixCommand<MyResponseDto> {
public MyHystrixCommand() {
super(HystrixCommandGroupKey.Factory.asKey("MyHystrixGroup"));
}
So for the above code group key is MyHystrixGroup and Command Key is MyHystrixCommand.
If i want to set any configurations of this hystrix command i can do like
ConfigurationManager.getConfigInstance().setProperty(
"hystrix.command.MyHystrixCommand.execution.timeout.enabled", false);
Where as the default ones will be,
ConfigurationManager.getConfigInstance().setProperty(
"hystrix.command.default.execution.timeout.enabled", false);
Now when I am using Feign Hystrix, I am not defining the command name/ group name. As per the documentation here, the group key matches the target name and command key are same as logging keys.
So if I have a FeignClient like this,
interface TestInterface {
#RequestLine("POST /")
String invoke() throws Exception;
}
I create the instance of my Feign client in a factory class.
class TestFactory {
public TestInterface newInstance() {
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 500);
return HystrixFeign.builder()
.target(TestInterface.class, "http://localhost:" + server.getPort(), (FallbackFactory) new FallbackApiRetro());
}
}
As you see before returning the client, i want to set the timeout configuration of my hystrix command.
I am testing it with a MockWebServer.
#Test
public void fallbackFactory_example_timeout_fail() throws Exception {
server.start();
server.enqueue(new MockResponse().setResponseCode(200)
.setBody("ABCD")
.setBodyDelay(1000, TimeUnit.MILLISECONDS));
TestFactory factory = new TestFactory();
TestInterface api = factory.newInstance();
// as the timeout is set to 500 ms, this case should fail since i added 1second delay in mock service response.
assertThat(api.invoke()).isEqualTo("Fallback called : foo");
}
This is working only when i set the time out on default hystrix paramater
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.invoke.execution.isolation.thread.timeoutInMilliseconds", 500);
This didn't work.
Similarly i tried below values none of them worked.
hystrix.command.TestInterface#invoke(String).execution.isolation.thread.timeoutInMilliseconds
hystrix.command.TestInterface#invoke.execution.isolation.thread.timeoutInMilliseconds
I figured it out.
ConfigurationManager.getConfigInstance().setProperty("hystrix.command.TestInterface#invoke().execution.isolation.thread.timeoutInMilliseconds",500);
is working. The mistake i did was my method name was not having any parameters passed in. So for a feign hystrix client, the command name is
FeignClientInterfaceName#MethodNameWithSignatures
For example quoted in the question, it is
TestInterface#invoke()
I am using a #Retryable annotation on a method in a #Serviceclass
#Service
#EnableRetry
public class PushService {
#Retryable(maxAttempts=5)
public Result pushIt(myMessage messageIn) {
...
}
}
and it works like a charme: I am getting a message directly from RabbitMQ, it is not acknowledged until either there is no error, or the number of attempts reach 5, and at that time the messages goes straight to the DLQ, right as I wanted.
My only problem is that I need to set the maxAttempts dynamically, from a property file. The solution should be setting an interceptor, but the only fact of having one causes an error, for example when I have :
#Service
#EnableRetry
public class PushService {
#Retryable(interceptor="myInterceptor")
public Result pushIt(myMessage messageIn) {
...
}
}
where myInterceptor is defined as :
#Bean
public StatefulRetryOperationsInterceptor myInterceptor() {
return RetryInterceptorBuilder.stateful().maxAttempts(5).build();
}
I get an infinite loop with the following exception:
2015-04-08 07:12:10,970 GMT [SimpleAsyncTaskExecutor-1] (ConditionalRejectingErrorHandler.java:67) WARN listener.ConditionalRejectingErrorHandler: 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:864)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:802)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:690)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$001(SimpleMessageListenerContainer.java:82)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.invokeListener(SimpleMessageListenerContainer.java:167)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:1241)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:660)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1005)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:989)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.java:82)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1103)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at org.springframework.amqp.rabbit.config.StatefulRetryOperationsInterceptorFactoryBean$3.getKey(StatefulRetryOperationsInterceptorFactoryBean.java:103)
at org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor.invoke(StatefulRetryOperationsInterceptor.java:132)
at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
at com.acme.push.service.PushService$$EnhancerBySpringCGLIB$$9d503bc1.pushMessage(<generated>)
at com.acme.push.receiver.PushListener.onMessage(PushListener.java:42)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:799)
... 10 more
I am pretty sure I am keeping it too simple, but I just have no clues on what could cause this error and how to solve it, anybody has an idea of what is going on ?
The purpose of org.springframework.amqp.rabbit.config.RetryInterceptorBuilder is not to be used using the annotation #Retryable.
Its purpose is to be used with the advice chain of the SimpleRabbitListenerContainerFactory class.
See the reference documentation receiving-messages and he SimpleMessageListenerContainer#invokeListener signature:
#Override
protected void invokeListener(Channel channel, Message message) throws Exception {
proxy.invokeListener(channel, message);
}
You annotation will be useless as soon as you configure the advice chain with the appropriate RetryInterceptor.
I finally managed to obtain the needed flexibility without using the #Retrayable annotation.
I created a RetryAdvice with my parameters for delay and maximum number of attempts:
#Bean
public MethodInterceptor retryAdvice() {
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(delay);
return RetryInterceptorBuilder.stateless().backOffPolicy(backOffPolicy)
.maxAttempts(maxAttempts).build();
}
and I inserted the Advice in the adviceChain of the ListenerContainer
#Bean
public SimpleMessageListenerContainer replyListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(pushConnectionFactory());
container.setQueues(pushQueue());
container.setMessageListener(pushListener());
Advice[] adviceChain = new Advice[] { retryAdvice() };
container.setAdviceChain(adviceChain);
return container;
}
In this way, whenever my Listener will be throwing
throw new AmqpRejectAndDontRequeueException(cause);
this will cause the container to retry the indicated number of times with the desired delay, after which the exception will be propagated and the message will be delivered in the DLQ
EDIT: You can ignore most of what I have written below:
I am getting a null value of context when I do the following in some TestNG code:
public void setupNonTrivialObjects() {
TestFixture.context = new MockServletContext("test");
}
Am I supposed to do something more to make a MockServletContext object that is not null?
ORIGINAL: I am learning to use the Stripes Framework with TestNG.
I am following the example here (but adapting it to my own code): http://www.stripesframework.org/display/stripes/Unit+Testing under the heading Approach 2
I have this test:
public class SeedSearchActionBeanTest {
#Test
public void seedSearchTest() throws Exception {
// Setup the servlet engine
MockServletContext ctx = TestFixture.getServletContext();
MockRoundtrip trip = new MockRoundtrip(ctx, SeedSearchActionBean.class);
trip.setParameter("input", "sdfs");
trip.execute();
SeedSearchActionBean bean = trip.getActionBean(SeedSearchActionBean.class);
Assert.assertEquals(bean.getInput(),"sdfs");
Assert.assertEquals(trip.getDestination(), "/results.jsp");
}
}
This "TestFixture" not really sure what that is.
public class TestFixture {
private static MockServletContext context;
#BeforeSuite
public void setupNonTrivialObjects() {
TestFixture.context = new MockServletContext("test");
// Add the Stripes Filter
Map<String,String> filterParams = new HashMap<String,String>();
filterParams.put("ActionResolver.Packages", "net.sourceforge.stripes");
context.addFilter(StripesFilter.class, "StripesFilter", filterParams);
// Add the Stripes Dispatcher
context.setServlet(DispatcherServlet.class, "StripesDispatcher", null);
}
public static MockServletContext getServletContext() {
return TestFixture.context;
}
}
I get this error
FAILED: seedSearchTest
java.lang.NullPointerException
at net.sourceforge.stripes.mock.MockRoundtrip.getUrlBindingStub(MockRoundtrip.java:384)
at net.sourceforge.stripes.mock.MockRoundtrip.<init>(MockRoundtrip.java:96)
at net.sourceforge.stripes.mock.MockRoundtrip.<init>(MockRoundtrip.java:82)
at sempedia.tests.action.SeedSearchActionBeanTest.seedSearchTest(SeedSearchActionBeanTest.java:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
I guess this line MockServletContext ctx = TestFixture.getServletContext(); is not working, I am wondering if there is something I am missing, particularly, is there something I have to do in the web.xml?
The mistake is with this line:
filterParams.put("ActionResolver.Packages", "net.sourceforge.stripes");`
This should be (in my case):
filterParams.put("ActionResolver.Packages", "action");
Essentially you are setting the package name where the ActionBeans are found. It seems very obvious once you know it.
You seem to be testing whether you've set up Stripes action bean creation and parameter passing which no doubt have been tested extensively by those developing the Stripes Framework. I tend to test the load/save/etc business logic (services) called from my actions.