Can I use Spring Data Redis #Repositories, and a CachingConfigurer to implement error handling? For instance -
#Override
public CacheErrorHandler errorHandler() {
return new RedisCacheError();
}
Using the CacheConfigurer above along with the repository as defined below, note the use of CrudRepository operations.
#Repository
public interface ObjectRepository extends CrudRepository<object, String>
I notice that CrudRepository has operations such as save, wondering if that is doing a redis.add() in the background? And basically whether or not the ErrorHandler see below, will actually catch errors on the save operation.
public class RedisCacheError implements CacheErrorHandler {
#Override
public void handleCacheGetError(RuntimeException exception,
Cache cache, Object key) {
log.info("caught error in here: " + exception );
}
#Override
public void handleCachePutError(RuntimeException exception, Cache cache,
Object key, Object value) {
log.info("caught error in here: " + exception );
}
#Override
public void handleCacheEvictError(RuntimeException exception, Cache cache,
Object key) {
//Do something on error while Evict cache
}
#Override
public void handleCacheClearError(RuntimeException exception,Cache cache){
//Do something on error while clearing cache
}
}
Related
I recently started exploring redisson for one of the use-case, where the map should hold string & queue of pojo objects. I tried to add the objects to queue for a particular key but when I'm retrieving it's values, it is giving empty results.
#Autowired
private RedissonClient redissonClient;
private RMapCache<String, Queue<AbstractXXXEvent>> mapCache;
#PostConstruct()
public void init() {
this.mapCache = redissonClient.getMapCache("test8", JsonJacksonCodec.INSTANCE);
}
#PreDestroy
public void cleanup() {
if (Objects.nonNull(redissonClient)) {
redissonClient.shutdown();
}
}
#RestController
class TestController {
#GetMapping("/get")
public Set<Map.Entry<String, Queue<AbstractXXXEvent>>> get() {
return mapCache.entrySet();
}
#PostMapping("/post")
public void post() {
mapCache.put("test", new ConcurrentLinkedQueue<>());
Queue<AbstractXXXEvent> queue = mapCache.get("test");
queue.add(new aXXXEvent().setDescription("compile done"));
queue.add(new bXXXEvent());
queue.add(new cXXXEvent().setDescription("completed"));
}
}
When I do a get request, it is giving the empty result with given key ex: [{"test":[]}]. Kindly provide your suggestions on how to fix this.
You need to put Redisson's RQueue instead of ConcurrentLinkedQueue
I'm doing some experiments with Blazor and want to set up logging. I see that Blazor logs to Microsoft.Extensions.Logging out of the box and that the log messages go to the developer console inside the browser. That is a nice start.
Now I want to try and log messages to other destinations as well. It could be a cloud-service. I'm wondering where to set that up. In ASP.NET Core, you would set it up using the ConfigureLogging method in Program.cs. But this isn't available with Blazor:
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
BlazorWebAssemblyHost.CreateDefaultBuilder()
.UseBlazorStartup<Startup>()
.ConfigureLogging(...); // <- compile error
As a fallback, I'm trying to set it up through ConfigureServices in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(builder => builder
.AddMyLogger()
.SetMinimumLevel(LogLevel.Information));
}
with AddMyLogger:
public static ILoggingBuilder AddMyLogger(this ILoggingBuilder builder)
{
builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
return builder;
}
and MyLoggerProvider:
public class MyLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new MyLogger();
}
public void Dispose()
{
}
}
and MyLogger:
public class MyLogger : ILogger
{
public MyLogger()
{
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
}
}
The AddMyLogger-method is called but my logger is never created or receives any Log-calls.
Am I doing something wrong here or is logging with Blazor WebAssembly simply not ready yet?
I was trying something similar. In my case, the Log method in MyLogger gets called; however it fails at following line of code
using (var streamWriter = new StreamWriter(fullFilePath, true)) //Fails here
{
streamWriter.WriteLine(logRecord);
}
When I put it in try catch block, I got the exception "Children could not be evaluated".
While researching I came across following link. Steve Sanderson's response might make sense of the behavior
Reading local files #16156
BTW It's been a long time, please let me know the solution you came up with.
Spring boot 2.1.5
Project Reactor 3.2.9
In my webflux project I extensively use the reactor contexts in order to pass around some values.
I set up a filter and am trying to log things which are in the context and to log different things in case of error/success.
I have checked this documentation: https://projectreactor.io/docs/core/release/reference/#context
I still struggle (especially on the error side) to get it.
Basically, I have this filter:
#Component
public class MdcWebFilter implements WebFilter {
#NotNull
#Override
public Mono<Void> filter(#NotNull ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
Mono<Void> filter = webFilterChain.filter(serverWebExchange);
return filter
.doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
#Override
public void accept(Void aVoid, Throwable throwable) {
//Here i would like to be able to access to the request's context
System.out.println("doAfterSuccessOrError:" + (throwable==null ? "OK" : throwable.getMessage())+"log the context");
}
})
.doOnEach(new Consumer<Signal<Void>>() {
#Override
public void accept(Signal<Void> voidSignal) {
//Here i have the context but i don't really know if i am in success or error
System.out.println("doOnEach:"+"Log OK/KO and the exception" + voidSignal.getContext());
}
})
.subscriberContext(context -> context.put("somevar", "whatever"));
}
}
I also tried with a flatMap() and a Mono.subscriberContext() but i am not sure how to plug correctly with the filter (especially in error).
What would be the best way to achieve this ?
I'm not sure whether it possible access request reactor context from within WebFilter. WebFilter context exists in another Mono chain.
But it is do possible to assosiate attributes with request and able to fetch these attributes during request life time RequestContextHolder for Reactive Web
Very similar to Servlet API.
Controller:
#GetMapping(path = "/v1/customers/{customerId}")
public Mono<Customer> getCustomerById(
#PathVariable("customerId") String customerId,
ServerWebExchange serverWebExchange)
{
serverWebExchange.getAttributes().put("traceId", "your_trace_id");
return customerService.findById(customerId);
}
WebFilter:
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// ...
String traceId = exchange.getAttributeOrDefault("traceId", "default_value_goes_here");
//...
return chain.filter(exchange);
}
I know this is probably not the cleanest of the solutions, but you could create a container class that would keep the context between your two callbacks.
You would store the context at doOnEach and then you would be able to load it back at doAfterSuccessOrError:
public Mono<Void> filter(#NotNull ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
#lombok.Data
class MyContextContainer {
private Context context;
}
MyContextContainer container = new MyContextContainer();
Mono<Void> filter = webFilterChain.filter(serverWebExchange);
return filter
.doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
#Override
public void accept(Void aVoid, Throwable throwable) {
// load the context here
Context context = container.getContext();
// then do your stuff here
}
})
.doOnEach(new Consumer<Signal<Void>>() {
#Override
public void accept(Signal<Void> voidSignal) {
// store the context here
container.setContext(voidSignal.getContext());
}
})
.subscriberContext(context -> context.put("somevar", "whatever"));
}
It doesn't need to be a class, really. It could be an AtomicReference, but you get the idea.
Again, this might be just a workaround. I believe there must be a better way to access the context.
we are currently trying to upgrade from Camel 2.19.0 to 2.20.0.
We have one timed routed which schedule autostart = false
#ConfigurationProperties
#Component
public class AdaptionService extends SpringRouteBuilder {
#Value("${control.timer.cron}")
private String timerCron;
#Override
public void configure() throws Exception {
from("quartz2://adaptionServices/UserUpdateTimer?cron=" + timerCron)
.routeId("scheduler").autoStartup(false)
.to("direct:route-userUpdate");
from("direct:route-userUpdate")
.routeId("adaption_service")
.log("Executing Adaption Service (timed)");
}
No in the CamelContextConfiguration I try to star the timer route (I know I could simply achieve this by autoStartup(true). It is just an example to abstract from more complex use case we have.
#Configuration
public class CamelApplicationContextConfiguration implements CamelContextConfiguration {
#Override
public void beforeApplicationStart(CamelContext camelContext) {
camelContext.setUseMDCLogging(true);
}
#Override
public void afterApplicationStart(CamelContext camelContext) {
try {
camelContext.startRoute("scheduler");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
That does not work. I checked startRoute(..) and appearently the routeService inside the camelContext is completely empty. With 2.19.0 this works without a flaw.
Any suggestions?
Regards
Holger
The Camel 2.20 release has changed the startup logic when using Spring / Spring-Boot with Camel. You can see that in the release notes.
However we should make CamelContextConfiguration call the afterApplicationStart at a later stage. And also document this API a bit better when its called:
I have logged a ticket about this: https://issues.apache.org/jira/browse/CAMEL-11945
I'm having two routes in two separated projects :
First route is setting the header with a data format bean name as a constant :
setHeader("dataFormatBeanName", constant("myFirstList"))
First route :
public class MyTest {
#Configuration
public static class MyTestConfig extends CamelConfiguration {
#Bean(name = "myFirstList")
public DataFormat getMyFirstListDataFormat() {
return new MyFirstListDataFormat();
}
#Bean(name = "mySecondList")
public DataFormat getMySecondListDataFormat() {
return new MySecondListDataFormat();
}
#Bean
public RouteBuilder route() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:testFirstDataFormat").setHeader("dataFormatBeanName", constant("myFirstList")).to("direct:myRoute");
from("direct:testSecondDataFormat").setHeader("dataFormatBeanName", constant("mySecondList")).to("direct:myRoute");
}
};
}
}
}
Second route is supposed to retrieve the bean name from the header and use it as a custom marshaller. Something like :
custom(header("dataFormatBeanName"))
(doesn't compile)
Anyone knows how I'm supposed to get my bean name from the header to use it in the custom method ?
#Component
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
final RouteDefinition routedefinition = this.from("direct:myRoute");
routedefinition.marshal().custom(??????????).to("netty4:tcp://{{route.address}}:{{port}}?textline=true&sync=true");
}
After a few more hours searching, here is the solution a found :
No changes in the first class.
Second class uses an anonymous DataFormat in which I retrieve the bean name from the header and get the spring bean from camel context before calling its marshal method.
The AbstractXxxDataFormat class belongs to project2 and is inherited by the Project1 DataFormat.
#Override
public void configure() throws Exception {
final RouteDefinition routedefinition = this.from("direct:myRoute");
routedefinition.marshal(new DataFormat() {
#Override
public void marshal(final Exchange exchange, final Object graph, final OutputStream stream) throws Exception {
AbstractXxxDataFormat myDataFormat = (AbstractGoalDataFormat) getContext().getRegistry().lookupByName(exchange.getIn().getHeader("dataFormatBeanName", String.class));
myDataFormat.marshal(exchange, graph, stream);
}
#Override
public Object unmarshal(final Exchange exchange, final InputStream stream) throws Exception {
return null;
}
});
routedefinition.to("netty4:tcp://{{route.address}}:{{port}}?textline=true&sync=true");
}
If there's any better solution available, I'll be interested.
Have you tried simple("${header.dataFormatBeanName}") to access the header?
Also, rather than passing the format bean name in a header in the first place, why not factor out each .marshal() call into two subroutes (one for formatBeanA and one for formatBeanB) and then call the appropriate subroute rather than setting the header in the first place? I believe this could be a cleaner approach.
If you really need to get it in the route as a variable (as opposed to a predicate to be used in the builder api) you could use an inline processor to extract it:
public class MyRouteBuilder extends RouteBuilder {
public void configure() throws Exception {
from("someEndpoint")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
String beanName = exchange.getHeader("beanNameHeader");
}
});
}
}
Just be careful of scope and concurrency when storing the extracted beanName however.
A collegue of mine (thanks to him) found the definite solution :
set bean name in the exchange properties :
exchange.setProperty("myDataFormat", "myDataFormatAutowiredBean");
retrieve the dataFormat bean with RecipientList pattern and (un)marshal :
routedefinition.recipientList(simple("dataformat:${property.myDataFormat}:marshal"));
routedefinition.recipientList(simple("dataformat:${property.myDataFormat}:unmarshal"));
Very concise and works just fine.