Resiliency4j CircuitBreaker tried to call circuitBreaker logic in AOP in order to achieve not to call circuit breaker when it is disabled in config - aop

Conditionally I want to switch the circuit breaker switch off/on by setting spring.cloud.circuitbreaker.resilience4j.enabled=false. My logic should stay intact from circuit-breaker logic.
I tried using the below demo example to extend to my requirements, I am trying to bind circuit breaker call on target method based on circuit breaker flag spring.cloud.circuitbreaker.resilience4j.enabled=true in application.property, true and false case. There could be a simpler way to achieve this, help me if any other solution than what I tried.
Example:
spring cloud circuit-breaker-resiliency4j example
Tried calling happy path - Work fine when there is no exception [response comes within 3 seconds as time limiter set to 3seconds in bean creation]
application.properties:
spring.cloud.circuitbreaker.resilience4j.enabled=true
spring.cloud.config.enabled=false
spring.cloud.config.import-check.enabled=false
spring.main.allow-bean-definition-overriding=true
Controller:
#GetMapping("/delay/{seconds}")
public Map delay(#PathVariable int seconds) {
return mockService.delay(seconds);
}
MockService:
#ApplyCircuitBreaker
public Map delay(int seconds) {
return rest.getForObject("https://httpbin.org/delay/" + seconds, Map.class);
}
Config class:
#Configuration
#ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled"}, matchIfMissing = true)
public class ResiliencyConfig {
#Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(3)).build())
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.build());
}
}
ApplyCircuitBreaker - Custom annotation to Apply circuit breaker only for required methods:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface ApplyCircuitBreaker {
}
AOP: CircuitBreakerAroundAspect:
#Aspect
#Component
#ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
"spring.cloud.circuitbreaker.resilience4j.reactive.enabled" }, matchIfMissing = true)
public class CircuitBreakerAroundAspect {
#Autowired
CircuitBreakerFactory circuitBreakerFactory;
#Around("#annotation(com.ravibeli.circuitbreaker.aspects.ApplyCircuitBreaker)")
public Object aroundAdvice(final ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Arguments passed to method are: {}", Arrays.toString(joinPoint.getArgs()));
AtomicReference<Map<String, String>> fallback = new AtomicReference<>();
Object proceed = circuitBreakerFactory.create(joinPoint.getSignature().toString())
.run(() -> {
try {
log.info("Inside CircuitBreaker logic in Aspect");
return joinPoint.proceed();
} catch (Throwable t) {
log.error(t.getMessage());
}
return null;
}, Throwable::getMessage);
log.info("Result from method is: {}", proceed);
return proceed;
}
}
My requirement:
circuitBreakerFactory.create(joinPoint.getSignature().toString()) .run(() -> ....) at this line, when target method throws exception, controll should go to fallback mechanism call. Since joinPoint.proceed() throws exception, it is forcing to handle exception - So I am doing wrong here, need suggestion to fix this to solve the requirement.
Error log:
{
"timestamp": "2021-07-10T01:33:10.558+00:00",
"status": 500,
"error": "Internal Server Error",
"trace": "java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap')\r\n\tat com.ravibeli.circuitbreaker.service.MockService$$EnhancerBySpringCGLIB$$3e293bd0.delay(<generated>)\r\n\tat com.ravibeli.circuitbreaker.controllers.DemoController.delay(DemoController.java:53)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:655)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:228)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1723)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:834)\r\n",
"message": "class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap')",
"path": "/delay/3"
}

You seem to be asking a couple different questions here.
The title seems to be asking why the aspect is still present when
spring.cloud.circuitbreaker.resilience4j.enabled=false
The problem is with your conditional
#ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
"spring.cloud.circuitbreaker.resilience4j.reactive.enabled" }, matchIfMissing = true)
It's simply requiring the property be present, it's not checking what it's set to. You need to set havingValue=true as well.
That said, I would strongly suggest not making your own pointcut for circuit breakers. Use the annotations provided by Resiliancy4j and just specify the fallback method there. I would expect that to clear up any other issues you're having with fallbacks.
#Bulkhead(name = 'myService', fallbackMethod = "myFallback")
#CircuitBreaker(name = 'myService', fallbackMethod = "myFallback")
#RateLimiter(name = 'myService', fallbackMethod = "myFallback")
#TimeLimiter(name = 'myService', fallbackMethod = "myFallback")

For enabling the circuit breaker dynamically you can use Profiles or Externalized Configuration (preferred approach would be to use Profiles and you can google more about them)
As far as your aspect's code goes, it looks and runs fine for me. Link to Code. It would be better if you could share the link to the code-base so that the issue can be investigated a bit further. Nevertheless, it seems a minor issue.

Thanks, guys for your comments, got the simple idea to fix this.
I resolved it with a custom factory implementation to make enable/disable feature working.
My GitHub example code: spring-cloud-resiliency4j

Related

Is this the correct way to process results through the Spring Integration Flow?

I am currently working on a personal project - in which I need my Spring application to take queries from an EMQX (MQTT Server) and query its data for corresponding results, and then push the results to a topic with the query UUID.
This is working - after many hours understanding how the Spring Integration framework works. But I think the way in which the handler is using "block" is incorrect - and not in keeping with the manner in which the Integration Flow should operate. Whilst this works I do want to make sure it is being done properly - out of respect for the work - and to avoid future issues.
The code snippet below should be enough to understand what it is that I'm trying to achieve - and where the potential issue lies.
#Bean
fun mqttInFlow() : Publisher<Message<String>> {
return IntegrationFlows.from(inbound())
.handle<String> { payload, headers ->
val emotionalOutput: EmotionalOutput = gson.fromJson(payload, EmotionalOutput::class.java)
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map {
MessageBuilder.withPayload(gson.toJson(it))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, "query/" + it.query_uuid).build()
}.block()
}
.channel(outgoingChannel())
.toReactivePublisher()
}
EDIT - Thanks for the advice - here is what I understood to be the potential edit for the Kotlin DSL solution - this is now producing an error - complaining that an output-channel or replyChannel was not available - nothing outside of the this function has been changed.
#Bean
fun newMqttInFlow() =
integrationFlow (inbound()) {
wireTap {
handle<String> { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid)
}
}
}
}
channel("outgoingChannel")
}
Exception is :
exception is org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
Although I have many years experience with Java - this approach is new - so thank you very much for your assistance. It's appreciated. If the whole class would be useful - I can post that.
EDIT
Here is the Configuration file - which might give a better insight into what might be causing this secondary error -
021-03-28 21:59:48.008 ERROR 84492 --- [T Call: divnrin] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessageHandlingException: error occurred in message handler [bean 'mqttOutbound'; defined in: 'class path resource [io/divnr/appserver/configuration/MQTTConfiguration.class]'; from source: 'org.springframework.core.type.classreading.SimpleMethodMetadata#4a9419d7']; nested exception is java.lang.IllegalArgumentException: This default converter can only handle 'byte[]' or 'String' payloads; consider adding a transformer to your flow definition, or provide a BytesMessageMapper, or subclass this converter for reactor.core.publisher.MonoMapFuseable payloads, failedMessage=GenericMessage [payload=MonoMapFuseable, headers={mqtt_receivedRetained=false, mqtt_id=0, mqtt_duplicate=false, id=c5a75283-c0fe-ebac-4168-dabddd989da9, mqtt_receivedTopic=source/d9e50e8f-67e0-4505-7ca2-4d05b1242207, mqtt_receivedQos=0, timestamp=1616961588004}]
at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:192)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:65)
at
The full class is provided here.
#Configuration
#EnableIntegration
#IntegrationComponentScan
class MQTTConfiguration(val emotionalPrintService: EmotionalPrintService,
val gson: Gson,
val applicationConfiguration: ApplicationConfiguration) {
#Bean
fun mqttServiceFactory() : MqttPahoClientFactory {
return DefaultMqttPahoClientFactory().apply {
connectionOptions = MqttConnectOptions().apply {
serverURIs = arrayOf<String>(applicationConfiguration.mqttServerAddress)
}
}
}
#Bean
fun newMqttInFlow() =
integrationFlow (inbound()) {
handle<String> { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid).build()
}
}
}
channel(outgoingChannel())
}
#Bean
#ServiceActivator(requiresReply = "false", inputChannel = "outgoingChannel")
fun mqttOutbound(): MessageHandler {
val messageHandler = MqttPahoMessageHandler("divnrout", mqttServiceFactory())
messageHandler.setAsync(true)
return messageHandler
}
#Bean
fun outgoingChannel() : FluxMessageChannel {
return FluxMessageChannel()
}
#Bean
fun inbound(): MessageProducerSupport {
return MqttPahoMessageDrivenChannelAdapter("divnrin", mqttServiceFactory(),
"source/" + applicationConfiguration.sourceUuid).apply {
setConverter(DefaultPahoMessageConverter())
setQos(1)
}
}
}
You indeed don't need that block() in the end of your handle(). You just can return the Mono from that emotionalPrintService.populateEmotionalOutput() and the framework will take for you about the proper subscription and back-pressure handling.
What you would need yet is to make that outgoingChannel() as a FluxMessageChannel.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/reactive-streams.html#reactive-streams
Plus consider to move your IntegrationFlow solution to the proper Kotlin DSL: https://docs.spring.io/spring-integration/docs/current/reference/html/kotlin-dsl.html#kotlin-dsl
Also: when it is a FluxMessageChannel in the end of flow, there is no reason to worry about a toReactivePublisher() - the FluxMessageChannel is a Publisher<Message<?>> by itself.
UPDATE
The problem is here:
handle<String>( { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid).build()
}
}
}) { async(true) }
See that async(true) option. Unfortunately in the current version we don't let it to process reactive reply in the reactive manner by default. You have to say that you'd like to be an async at this end-point. So, your Publisher reply and and FluxMessageChannel as an output is going to do the proper trick.

How to Take Screenshot when TestNG Assert fails?

String Actualvalue= d.findElement(By.xpath("//[#id=\"wrapper\"]/main/div[2]/div/div[1]/div/div[1]/div[2]/div/table/tbody/tr[1]/td[1]/a")).getText();
Assert.assertEquals(Actualvalue, "jumlga");
captureScreen(d, "Fail");
The assert should not be put before your capture screen. Because it will immediately shutdown the test process so your code
captureScreen(d, "Fail");
will be not reachable
This is how i usually do:
boolean result = false;
try {
// do stuff here
result = true;
} catch(Exception_class_Name ex) {
// code to handle error and capture screen shot
captureScreen(d, "Fail");
}
# then using assert
Assert.assertEquals(result, true);
1.
A good solution will be is to use a report framework like allure-reports.
Read here:allure-reports
2.
We don't our tests to be ugly by adding try catch in every test so we will use Listeners which are using an annotations system to "Listen" to our tests and act accordingly.
Example:
public class listeners extends commonOps implements ITestListener {
public void onTestFailure(ITestResult iTestResult) {
System.out.println("------------------ Starting Test: " + iTestResult.getName() + " Failed ------------------");
if (platform.equalsIgnoreCase("web"))
saveScreenshot();
}
}
Please note I only used the relevant method to your question and I suggest you read here:
TestNG Listeners
Now we will want to take a screenshot built in method by allure-reports every time a test fails so will add this method inside our listeners class
Example:
#Attachment(value = "Page Screen-Shot", type = "image/png")
public byte[] saveScreenshot(){
return ((TakesScreenshot)driver).getScreenshotAs(OutputType.BYTES);
}
Test example
#Listeners(listeners.class)
public class myTest extends commonOps {
#Test(description = "Test01: Add numbers and verify")
#Description("Test Description: Using Allure reports annotations")
public void test01_myFirstTest(){
Assert.assertEquals(result, true)
}
}
Note we're using at the beginning of the class an annotation of #Listeners(listeners.class) which allows our listeners to listen to our test, please mind the (listeners.class) can be any class you named your listeners.
The #Description is related to allure-reports and as the code snip suggests you can add additional info about the test.
Finally, our Assert.assertEquals(result, true) will take a screen shot in case the assertion fails because we enabled our listener.class to it.

Can Spring-Data-Rest handle associations to Resources on other Microservices?

For a new project i'm building a rest api that references resources from a second service. For the sake of client convenience i want to add this association to be serialized as an _embedded entry.
Is this possible at all? i thought about building a fake CrudRepository (facade for a feign client) and manually change all urls for that fake resource with resource processors. would that work?
a little deep dive into the functionality of spring-data-rest:
Data-Rest wraps all Entities into PersistentEntityResource Objects that extend the Resource<T> interface that spring HATEOAS provides. This particular implementation has a list of embedded objects that will be serialized as the _embedded field.
So in theory the solution to my problem should be as simple as implementing a ResourceProcessor<Resource<MyType>> and add my reference object to the embeds.
In practice this aproach has some ugly but solvable issues:
PersistentEntityResource is not generic, so while you can build a ResourceProcessor for it, that processor will by default catch everything. I am not sure what happens when you start using Projections. So that is not a solution.
PersistentEntityResource implements Resource<Object> and as a result can not be cast to Resource<MyType> and vice versa. If you want to to access the embedded field all casts have to be done with PersistentEntityResource.class.cast() and Resource.class.cast().
Overall my solution is simple, effective and not very pretty. I hope Spring-Hateoas gets full fledged HAL support in the future.
Here my ResourceProcessor as a sample:
#Bean
public ResourceProcessor<Resource<MyType>> typeProcessorToAddReference() {
// DO NOT REPLACE WITH LAMBDA!!!
return new ResourceProcessor<>() {
#Override
public Resource<MyType> process(Resource<MyType> resource) {
try {
// XXX all resources here are PersistentEntityResource instances, but they can't be cast normaly
PersistentEntityResource halResource = PersistentEntityResource.class.cast(resource);
List<EmbeddedWrapper> embedded = Lists.newArrayList(halResource.getEmbeddeds());
ReferenceObject reference = spineClient.findReferenceById(resource.getContent().getReferenceId());
embedded.add(embeddedWrappers.wrap(reference, "reference-relation"));
// XXX all resources here are PersistentEntityResource instances, but they can't be cast normaly
resource = Resource.class.cast(PersistentEntityResource.build(halResource.getContent(), halResource.getPersistentEntity())
.withEmbedded(embedded).withLinks(halResource.getLinks()).build());
} catch (Exception e) {
log.error("Something went wrong", e);
// swallow
}
return resource;
}
};
}
If you would like to work in type safe manner and with links only (addition references to custom controller methods), you can find inspiration in this sample code:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.RepresentationModelProcessor;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
#Configuration
public class MyTypeLinkConfiguration {
public static class MyType {}
#Bean
public RepresentationModelProcessor<EntityModel<MyType>> MyTypeProcessorAddLifecycleLinks(MyTypeLifecycleStates myTypeLifecycleStates) {
// WARNING, no lambda can be passed here, because type is crucial for applying this bean processor.
return new RepresentationModelProcessor<EntityModel<MyType>>() {
#Override
public EntityModel<MyType> process(EntityModel<MyType> resource) {
// add custom export link for single MyType
myTypeLifecycleStates
.listReachableStates(resource.getContent().getState())
.forEach(reachableState -> {
try {
// for each possible next state, generate its relation which will get us to given state
switch (reachableState) {
case DRAFT:
resource.add(linkTo(methodOn(MyTypeLifecycleController.class).requestRework(resource.getContent().getId(), null)).withRel("requestRework"));
break;
case IN_REVIEW:
resource.add(linkTo(methodOn(MyTypeLifecycleController.class).requestReview(resource.getContent().getId(), null)).withRel("requestReview"));
break;
default:
throw new RuntimeException("Link for target state " + reachableState + " is not implemented!");
}
} catch (Exception ex) {
// swallowed
log.error("error while adding lifecycle link for target state " + reachableState + "! ex=" + ex.getMessage(), ex);
}
});
return resource;
}
};
}
}
Note, that myTypeLifecycleStates is autowired "service"/"business logic" bean.

Spring-AOP load-time weaving on 3rd-party classes

I wrote an aspect that I'm trying to test with junit.
The aspect has an #Around advice on a 3rd party method called setQuery.
At compile time it complains: Can't find referenced pointcut setQuery
Here's my aspect:
#Component
#Aspect
public class ElasticsearchQuerySecurityAspect {
#Around("org.elasticsearch.action.search.SearchRequestBuilder.setQuery() && args(queryBuilder)")
public void addFilter(final ProceedingJoinPoint pjp, QueryBuilder queryBuilder) throws Throwable {
Object[] args = pjp.getArgs();
// Set the filter to use our plugin
FilterBuilder securityFilter = FilterBuilders.scriptFilter("visibility-filter")
.lang("native")
.addParam("visibility-field", "visibility")
.addParam("parameter", "default");
// Re-create original query with the filter applied
QueryBuilder newQuery = QueryBuilders.filteredQuery(queryBuilder,securityFilter);
log.info("Adding filter to search request");
// Tell the method to run with the modified parameter
args[0] = newQuery;
pjp.proceed(args);
}
}
Here's my junit test:
#RunWith(SpringJUnit4ClassRunner.class)// NOTE #1
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
#EnableLoadTimeWeaving
#ComponentScan
public class ElasticsearchQuerySecurityTest {
Client client = mock(Client.class);
#Before
public void setUp() throws Exception {
}
#Test
public void test() {
SearchRequestBuilder s = new SearchRequestBuilder(client);
QueryBuilder qb = QueryBuilders.queryString("name:foo");
XContentBuilder builder;
try {
builder = XContentFactory.jsonBuilder();
qb.toXContent(builder, null);
assertEquals("{\"query_string\":{\"query\":\"name:foo\"}}",builder.string());
// Call setQuery() which will invoke the security advice and add a filter to the query
s.setQuery(qb);
builder = XContentFactory.jsonBuilder().startObject();
qb.toXContent(builder, null);
builder.endObject();
assertEquals("{\"query\": "+
"{ \"filtered\": "+
"{ \"query\": "+
"{ \"query_string\": "+
"{ \"name:foo\", } }, "+
"\"filter\": "+
"{ \"script\": "+
"{ \"script\": \"visibility-filter\","+
"\"lang\":\"native\", "+
"\"params\": "+
"{ \"visibility-field\":\"visibility\", "+
"\"parameter\":\"default\" } } } } } }",
builder.string());
} catch (IOException e) {
fail("We threw an I/O exception!");
}
}
}
I also have this aop.xml on the classpath:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<include within="org.elasticsearch.action.search.*"/>
</weaver>
<aspects>
<aspect name="org.omaas.security.ElasticsearchQuerySecurityAspect"/>
</aspects>
</aspectj>
I tried an aspect with #Around("execution(public * set*())") and found that it only advised stuff in the current package. How do I get it to be applied to stuff in the 3rd-party package?
Spring AOP can only weave into Spring Beans. As your 3rd party target class is not a Spring bean, there is no way to apply an aspect to it. For that purpose you need to use AspectJ which is way more powerful and does not rely on Spring's "AOP lite" implementation based on dynamic proxies.
With AspectJ you have two options:
Compile-time weaving (CTW): You can compile aspects into the 3rd party classes and create a new, aspect-enhanced JAR for your dependency.
Load-time weaving (LTW): You can weave aspects into the 3rd party classes while they are being loaded at runtime. This takes a few CPU cycles while bootstrapping your application, but spares you from having to re-package the 3rd party JAR.
Edit: Oh, by the way, your pointcut syntax is invalid. You cannot write
#Around("org.elasticsearch.action.search.SearchRequestBuilder.setQuery() && args(queryBuilder)")
Instead you rather need something like
#Around("execution(* org.elasticsearch.action.search.SearchRequestBuilder.setQuery(*)) && args(queryBuilder)")
A method name is not enough, you have to tell the AOP framework that you want to capture its execution() (in AspectJ cou could also capture all its callers via call()). Secondly, you will not capture a method with one QueryBuilder parameter by specifying a method signature setQuery() without any parameters, thus I suggest you use setQuery(*) or, if you want to be even more precise, setQuery(org.elasticsearch.index.query.QueryBuilder). You also need a return type and/or modifier like public in front of the method name or again a joker like *.

Should a class be able to catch an exception from a class that it doesn't know about?

I wrote some code in an MVC Framework that looks something like:
class Controller_Test extends Controller
{
public function action_index()
{
$obj = new MyObject();
$errors = array();
try
{
$results = $obj->doSomething();
}
catch(MyObject_Exception $e)
{
$e->getErrors();
}
catch(Exception $e)
{
$errors[] = $e->getMessage();
}
}
My friend argues that the Controller should know nothing about MyObject, and therefore I should not catch MyObject_Exception.
He argues that the code should do something like this instead:
class Controller_Test extends Controller
{
public function action_index()
{
$obj = new MyObject();
$errors = array();
if($obj->doSomething())
{
$results = $obj->getResults();
}
else
{
$errors = $obj->getErrors();
}
}
I definitely understand his approach, but feel as though state management can lead to unintended side effects.
What is the right or preferred approach?
Edit: mistakenly put $obj->getErrors() in MyObject_Exception catch clause instead of $e->getErrors();
The debate about exceptions vs. returned error codes is a long and bloody one.
His argument breaks down in that, by using a getErrors() function, you are learning information about the object. If that is your reason for using a boolean return to indicate success, then you are wrong. In order for the Controller to handle the error properly, it has to know about the object it was touching and what the specific error was. Was it a network error? Memory error? It has to know in some way or another.
I prefer the exception model because it's cleaner and allows me to handle more errors in a more controlled fashion. It also provides a clear cut way for the data relating to an exception to be passed.
However, I disagree with your use of a function like getErrors(). Any data pertaining to the exception that would help me handle it should be included with the exception. I should not have to go hunting into the object again to get information about what went wrong.
Did the network connection timeout? The exception should contain the host/port it tried to connect to, how long it waited, and any data from the lower networking levels.
Let's do this in example (in psuedo c#):
public class NetworkController {
Socket MySocket = null;
public void EstablishConnection() {
try {
this.MySocket = new Socket("1.1.1.1",90);
this.MySocket.Open();
} catch(SocketTimeoutException ex) {
//Attempt a Single Reconnect
}
catch(InvalidHostNameException ex) {
Log("InvalidHostname");
Exit();
}
}
}
Using his method:
public class NetworkController {
Socket MySocket = null;
public Boolean EstablishConnection() {
this.MySocket = new Socket("1.1.1.1",90);
if(this.MySocket.Open()) {
return true;
} else {
switch(this.MySocket.getError()) {
case "timeout":
// Reattempt
break;
case "badhost":
Log("InvalidHostname");
break;
}
}
}
}
Ultimately, you need to know what happened to the object to know how to respond to it, and there is no sense in using some convoluted if statement set or switch-case to determine that. Use the exceptions and love them.
EDIT: I accidentally the last half of a sentence.
In general, I would say that what's important is whether the controller understands the meaning of the exception and can handle it properly. In many cases (if not most), the controller will not know how to properly handle the exception, and so should not catch and handle it.
On the other hand, the controller might reasonably be permitted to understand some specific exception like a "DatabaseUnavailableException", even if it has no idea how or why MyObject used a database. The controller might be permitted to retry the call to MyObject a certain number of times, all without knowing about how MyObject is implemented.
First of all controller is not meant for handling the underlying exceptions thrown by classes.
Even if one occurs controller should halt saying something wrong at underlying error.
This way we make sure that controller does really and only do the job of flow control.
The other classes which give controller some output should be error free unless the error is very much controller specific.