Is it possible to Inject the result of a producer method in JAX-RS request? - jax-rs

I have an utility class :
#RequestScoped
public class StatsServicesDataImpl {
#Qualifier
#Retention(RUNTIME)
#Target({ METHOD, PARAMETER })
public #interface Lines {
}
#Qualifier
#Retention(RUNTIME)
#Target({ METHOD, PARAMETER })
public #interface Versions {
}
public StatsServicesDataImpl() {
}
#Produces
#Versions
#Transactional(TxType.REQUIRES_NEW)
public String getVersionsAsJsonString() {
log.debug("CDI calls getVersionsAsJsonString");
...
return json.toString();
}
#Produces
#Lines
#Transactional(TxType.REQUIRES_NEW)
public String getLinesAsJsonString() {
log.debug("CDI calls getLinesAsJsonString");
...
return json.toString();
}
}
And then a JAX-RS class with :
#GET
#Path("/versions")
#Produces(MediaType.APPLICATION_JSON)
#Inject
public Response getVersions(#Versions String versions) {
log.debug("Endpoint versions :" + versions);
return Response.ok(versions).build();
}
#GET
#Path("/lines")
#Produces(MediaType.APPLICATION_JSON)
#Inject
public Response getLines(#Lines String lines) {
log.debug("Endpoint lines :" + lines);
return Response.ok(lines).build();
}
It doesn't work as expected :(
I wished that when I call the URL lines, CDI get the lines (#Inject and #Lines) and I have my JSON.
But when I call lines (http://...../lines) I get :
CDI calls getLinesAsJsonString
Endpoint lines :{"lignes":[{"id":97,"idu":1,"nom":"01"},{"id":98,"idu":2,"nom":"02"},{"id":99,"idu":3,"nom":"03"},{"id":100,"idu":4,"nom":"04"},{"id":101,"idu":5,"nom":"05"}]}
CDI calls getVersionsAsJsonString
Endpoint versions :{"versions":[{"id":71,"vdhId":19,"vdrId":18,"vdpId":18,"vdhNom":"Importé le 2017-08-10 Septembre 2017 V1","vdhDateExp":"Sep 1, 2017 12:00:00 AM","jexDateDeb":"Sep 1, 2017 12:00:00 AM","jexDateFin":"Aug 10, 2018 12:00:00 AM","downloadedDate":"mai 1, 2018"}]}
Endpoint lines :
And when I call versions :
CDI calls getLinesAsJsonString
Endpoint lines :{"lignes":[{"id":97,"idu":1,"nom":"01"},{"id":98,"idu":2,"nom":"02"},{"id":99,"idu":3,"nom":"03"},{"id":100,"idu":4,"nom":"04"},{"id":101,"idu":5,"nom":"05"}]}
CDI calls getVersionsAsJsonString
Endpoint versions :{"versions":[{"id":71,"vdhId":19,"vdrId":18,"vdpId":18,"vdhNom":"Importé le 2017-08-10 Septembre 2017 V1","vdhDateExp":"Sep 1, 2017 12:00:00 AM","jexDateDeb":"Sep 1, 2017 12:00:00 AM","jexDateFin":"Aug 10, 2018 12:00:00 AM","downloadedDate":"mai 1, 2018"}]}
Endpoint versions :
Same result :( As if both producer methods were called ...
Any idea how to combine JAX-RS and CDI so I can inject the result of a request (produced by CDI) in the response of the Rest-request ?

Related

Around annotion executed twice using WebFlux

I'm facing a weird behaviour while using AOP with AspectJ.
Basically the #Around method its called either once either twice and while trying to debugging I can't find the reason why it's being executing twice (I mean what triggers the second execution of the method)
here is some code :
#Aspect
#Slf4j
public class ReactiveRedisCacheAspect {
#Pointcut("#annotation(com.xxx.xxx.cache.aop.annotations.ReactiveRedisCacheable)")
public void cacheablePointCut() {}
#Around("cacheablePointCut()")
public Object cacheableAround(final ProceedingJoinPoint proceedingJoinPoint) {
log.debug("ReactiveRedisCacheAspect cacheableAround.... - {}", proceedingJoinPoint);
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
Class<?> returnTypeName = method.getReturnType();
Duration duration = Duration.ofHours(getDuration(method));
String redisKey = getKey(method, proceedingJoinPoint);
if (returnTypeName.isAssignableFrom(Flux.class)) {
log.debug("returning Flux");
return cacheRepository.hasKey(redisKey)
.filter(found -> found)
.flatMapMany(found -> cacheRepository.findByKey(redisKey))
.flatMap(found -> saveFlux(proceedingJoinPoint, redisKey, duration));
} else if (returnTypeName.isAssignableFrom(Mono.class)) {
log.debug("Returning Mono");
return cacheRepository.hasKey(redisKey)
.flatMap(found -> {
if (found) {
return cacheRepository.findByKey(redisKey);
} else {
return saveMono(proceedingJoinPoint, redisKey, duration);
}
});
} else {
throw new RuntimeException("non reactive object supported (Mono,Flux)");
}
}
private String getKey(final Method method, final ProceedingJoinPoint proceedingJoinPoint) {
ReactiveRedisCacheable annotation = method.getAnnotation(ReactiveRedisCacheable.class);
String cacheName = annotation.cacheName();
String key = annotation.key();
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
return cacheName + "_" + key;
}
}
public class AspectSupportUtils {
private static final ExpressionEvaluator evaluator = new ExpressionEvaluator();
public static Object getKeyValue(JoinPoint joinPoint, String keyExpression) {
if (keyExpression.contains("#") || keyExpression.contains("'")) {
return getKeyValue(joinPoint.getTarget(), joinPoint.getArgs(), joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), keyExpression);
}
return keyExpression;
}
private static Object getKeyValue(Object object, Object[] args, Class<?> clazz, Method method, String keyExpression) {
if (StringUtils.hasText(keyExpression)) {
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.key(keyExpression, methodKey, evaluationContext);
}
return SimpleKeyGenerator.generateKey(args);
}
}
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ReactiveRedisCacheable {
String key();
String cacheName();
long duration() default 1L;
}
#RestController
#RequestMapping("api/pub/v1")
public class TestRestController{
#ReactiveRedisCacheable(cacheName = "test-cache", key = "#name", duration = 1L)
#GetMapping(value = "test")
public Mono<String> getName(#RequestParam(value = "name") String name){
return Mono.just(name);
}
}
#Configuration
public class Config {
#Bean
public ReactiveRedisCacheAspect reactiveRedisCache (ReactiveRedisCacheAspect reactiveRedisCacheAspect) {
return reactiveRedisCacheAspect;
}
}
logs:
ReactiveRedisCacheAspect cacheableAround.... - {}execution(Mono com.abc.def.xxx.rest.TestRestcontroller.getName(String))
2021-06-04 15:36:23.096 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ctor-http-nio-3] c.m.s.c.a.i.ReactiveRedisCacheAspect : Returning Mono
2021-06-04 15:36:23.097 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ctor-http-nio-3] c.m.s.c.repository.CacheRepositoryImpl : searching key: (bff_pippo)
ReactiveRedisCacheAspect cacheableAround.... - {}execution(Mono com.abc.def.xxx.rest.TestRestcontroller.getName(String))
2021-06-04 15:36:23.236 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.a.i.ReactiveRedisCacheAspect : Returning Mono
2021-06-04 15:36:23.236 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : searching key: (bff_pippo)
2021-06-04 15:36:23.250 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : saving obj: (key:bff_pippo) (expiresIn:3600s)
2021-06-04 15:36:23.275 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : saving obj: (key:bff_pippo) (expiresIn:3600s)
So far I would have expected the cacheableAround would be executed only once, but what happens its a bit weird, if the object is present on redis the method is executed only once but if is not present the method is executed twice which it doesn't make sense, moreover it should be the business logic to manage what to do inside the method.
Thanks in advance!
You did not mention whether you use native AspectJ via load- or compile-time weaving or simply Spring AOP. Because I see not #Component annotation on your aspect, it might as well be native AspectJ, unless you configure your beans via #Bean factory methods in a configuration class or XML.
Assuming that you are using full AspectJ, a common problem newbies coming from Spring AOP have, is that they are not used to the fact that AspectJ not only intercepts execution joinpoints, but also call ones. This leads to the superficial perception that the same joinpoint is intercepted twice. But in reality, it is once the method call (in the class from which the call is made) and once the method execution (in the class where the target method resides). This is easy to determine if at the beginning of your advice method you simply log the joinpoint. In your case:
System.out.println(proceedingJoinPoint);
If then on the console you see something like
call(public void org.acme.MyClass.myMethod())
execution(public void org.acme.MyClass.myMethod())
then you know what is happening.
In case you use Spring AOP, probably it is an issue with the aspect or the Redis caching behaviour that is different from your expectation.

JAX-RS Response.getEntity() always null

I need help with Arquillian test.
Add code example of situation.
This code is working ok in real environment. Only in test case made with arquillian the result is not expected
The code:
#Stateless
public class CustomerResourceImpl implements CustomerResource{
#Override
public Response findOne(String id) {
String res = "Un cliente";
return Response.ok(res).build();
}
}
#Path("customer")
#Produces(MediaType.APPLICATION_JSON)
public interface CustomerResource {
#GET
#Path("/findOne")
public javax.ws.rs.core.Response findOne(#QueryParam("id") String id);
}
And this test case
#RunWith(Arquillian.class)
public class CustomerResourceTest {
#Deployment (testable = false)
public static Archive createTestArchive() {
return ShrinkWrap
..... (mas)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
}
#ArquillianResource
private URL deploymentURL;
#Test
#RunAsClient
public void findOne(#ArquillianResteasyResource CustomerResource resource) throws Exception {
final Response response = resource.findOne("1");
System.out.println(response.getEntity()); // IS NULL ??
System.out.println(response.getStatus()); // 200 OK
assertNotNull(response);
}
}
The problem is that response.getEntity() is always NULL . Why? The status response OK = 200 , it is OK. This service run ok in jboss 7.2 with Java 8.
Thanks!
The reason is #Deployment (testable = false)
#Deployment says:
testable = Defines if this deployment should be wrapped up based on the protocol so the testcase can be executed incontainer.
So false means it will not be deployed in the container, and therefore will be null when your tests run inside the container.
I recommend using #Deployment with not parameters passed in rather than setting testable = true
I just solved this today after a days of fiddling. I think I pasted the #Deployment (testable = false) code from examples on the internet that I didn't understand hoping to get something working.

How do I hook into micronaut server on error handling from a filter?

For any 4xx or 5xx response given out by my micronaut server, I'd like to log the response status code and endpoint it targeted. It looks like a filter would be a good place for this, but I can't seem to figure out how to plug into the onError handling
for instance, this filter
#Filter("/**")
class RequestLoggerFilter: OncePerRequestHttpServerFilter() {
companion object {
private val log = LogManager.getLogger(RequestLoggerFilter::class.java)
}
override fun doFilterOnce(request: HttpRequest<*>, chain: ServerFilterChain): Publisher<MutableHttpResponse<*>>? {
return Publishers.then(chain.proceed(request), ResponseLogger(request))
}
class ResponseLogger(private val request: HttpRequest<*>): Consumer<MutableHttpResponse<*>> {
override fun accept(response: MutableHttpResponse<*>) {
log.info("Status: ${response.status.code} Endpoint: ${request.path}")
}
}
}
only logs on a successful response and not on 4xx or 5xx responses.
How would i get this to hook into the onError handling?
You could do the following. Create your own ApplicationException ( extends RuntimeException), there you could handle your application errors and in particular how they result into http error codes. You exception could hold the status code as well.
Example:
class BadRequestException extends ApplicationException {
public HttpStatus getStatus() {
return HttpStatus.BAD_REQUEST;
}
}
You could have multiple of this ExceptionHandler for different purposes.
#Slf4j
#Produces
#Singleton
#Requires(classes = {ApplicationException.class, ExceptionHandler.class})
public class ApplicationExceptionHandler implements ExceptionHandler<ApplicationException, HttpResponse> {
#Override
public HttpResponse handle(final HttpRequest request, final ApplicationException exception) {
log.error("Application exception message={}, cause={}", exception.getMessage(), exception.getCause());
final String message = exception.getMessage();
final String code = exception.getClass().getSimpleName();
final ErrorCode error = new ErrorCode(message, code);
log.info("Status: ${exception.getStatus())} Endpoint: ${request.path}")
return HttpResponse.status(exception.getStatus()).body(error);
}
}
If you are trying to handle Micronaut native exceptions like 400 (Bad Request) produced by ConstraintExceptionHandler you will need to Replace the beans to do that.
I've posted example here how to handle ConstraintExceptionHandler.
If you want to only handle responses itself you could use this mapping each response code (example on #Controller so not sure if it works elsewhere even with global flag:
#Error(status = HttpStatus.NOT_FOUND, global = true)
public HttpResponse notFound(HttpRequest request) {
<...>
}
Example from Micronaut documentation.
Below code I used for adding custom cors headers in the error responses, in doOnError you can log errors
#Filter("/**")
public class ResponseCORSAdder implements HttpServerFilter {
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
return this.trace(request)
.switchMap(aBoolean -> chain.proceed(request))
.doOnError(error -> {
if (error instanceof MutableHttpResponse<?>) {
MutableHttpResponse<?> res = (MutableHttpResponse<?>) error;
addCorsHeaders(res);
}
})
.doOnNext(res -> addCorsHeaders(res));
}
private MutableHttpResponse<?> addCorsHeaders(MutableHttpResponse<?> res) {
return res
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "OPTIONS,POST,GET")
.header("Access-Control-Allow-Credentials", "true");
}
private Flowable<Boolean> trace(HttpRequest<?> request) {
return Flowable.fromCallable(() -> {
// trace logic here, potentially performing I/O
return true;
}).subscribeOn(Schedulers.io());
}
}

Spring Batch - Unable to deserialize the execution context - OffsetDateTime - cannot deserialize

I'm trying to create a spring batch job with multiples steps and passing object from step to step.
To do this I use ExecutionContext that i promoted from step to job context.
At first run, no problem data goes right from step to step
At next runs, I get the error :
"Unable to deserialize the execution context" Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of java.time.OffsetDateTime (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
I write context in a ItemWriter like so :
#Override
public void write(List<? extends Employee> items) throws Exception {
ExecutionContext stepContext = this.stepExecution.getExecutionContext();
List<Employee> e = new ArrayList<Employee>();
e.addAll(items);
stepContext.put("someKey", e);
}
And read it back in a ItemReader (from another step) with :
#BeforeStep
public void retrieveInterstepData(StepExecution stepExecution) {
JobExecution jobExecution = stepExecution.getJobExecution();
ExecutionContext jobContext = jobExecution.getExecutionContext();
this.someObject = (List<Employee>) jobContext.get("someKey");
}
I check spring database context and my dates (LocalDate, OffsetDateTime, ...) are store like :
"LocalDate": {
"year": 2019,
"month": "OCTOBER",
"dayOfMonth": 30,
"monthValue": 10,
"era": ["java.time.chrono.IsoEra", "CE"],
"dayOfWeek": "WEDNESDAY",
"dayOfYear": 303,
"leapYear": false,
"chronology": {
"id": "ISO",
"calendarType": "iso8601"
}
}
"OffsetDateTime": {
"offset": {
"totalSeconds": 0,
"id": "Z",
"rules": {
"fixedOffset": true,
"transitionRules": ["java.util.Collections$UnmodifiableRandomAccessList", []],
"transitions": ["java.util.Collections$UnmodifiableRandomAccessList", []]
}
},
"month": "OCTOBER",
"year": 2019,
"dayOfMonth": 28,
"hour": 13,
"minute": 42,
"monthValue": 10,
"nano": 511651000,
"second": 36,
"dayOfWeek": "MONDAY",
"dayOfYear": 301
}
I guess it's jackson's choice to store it like that (I custom nothing)
But it seems that jackson can't read it's own format at next run ?!
My stubs are generated with from swagger with "swagger-codegen-maven-plugin" and configOptions/dateLibrary=java8 so I can't change them.
I tried to add
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
And
#PostConstruct
public void init() {
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
In the #SpringBootApplication
no change
Any ideas ? Either to store dates more simply like "2019-11-04" or make jackson read it's own format ?
Your object mapper should be set on the Jackson2ExecutionContextStringSerializer used by the job repository. You can extend DefaultBatchConfigurer and override createJobRepository:
#Bean
public JobRepository createJobRepository() throws Exception {
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Jackson2ExecutionContextStringSerializer defaultSerializer = new Jackson2ExecutionContextStringSerializer();
defaultSerializer.setObjectMapper(objectMapper);
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setSerializer(defaultSerializer);
factory.afterPropertiesSet();
return factory.getObject();
}
EDIT :
My bad I just saw that I have a
#Bean
public BatchConfigurer batchConfigurer(#Qualifier("batchDataSource") DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource);
}
That provide 2 batchConfigurer to spring.
Thanks !
ORIGINAL :
Thanks it seems promising.
But I dont find where to extends and use it, on which class.
I have a Batch Class configuration :
#Configuration
#EnableConfigurationProperties(BatchProperties.class)
public class BatchDatabaseConfiguration {
#Value("${spring.datasource.driver-class-name}")
private String driverClassName;
#Value("${spring.datasource.url}")
private String dbURL;
#Bean("batchDataSource")
public DataSource batchDataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(dbURL);
return dataSource;
}
#Bean
public BatchConfigurer batchConfigurer(#Qualifier("batchDataSource") DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource);
}
#Bean(name = "batchTransactionManager")
public PlatformTransactionManager batchTransactionManager(#Qualifier("batchDataSource") DataSource dataSource) {
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource);
return tm;
}
}
And a Class with Job's definition :
#Configuration
#EnableBatchProcessing
public class ExtractionJobConfiguration {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Bean
public Job creationJob() {
...
}
[...]
}
And the main :
#EntityScan(basePackages = { "..." })
#SpringBootApplication
#EnableAsync
public class App {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(App.class, args);
}
What do you think ?
I also read that Spring Batch 4.2.0+ allow for customisation of ObjectMapper in Jackson2ExecutionContextStringSerializer (https://jira.spring.io/browse/BATCH-2828)
Is that what you propose ? (I don't find other information)

JAX-RS 2.0 doesn't throw exception when there is invalid attribute type in JSON request

I have the following REST endpoint:
#Stateless
#Path("test")
public class TestResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public TestDTO test(TestDTO dto) {
return dto;
}
}
The TestDTO bean is really simple:
public class TestDTO {
private String id;
private Integer number;
// + getter/setter
}
If I post the following JSON, the response will be exactly the same (as expected in this trivial sample):
{
"id": "abc",
"number": 123
}
But if I send a string value for "number":
{
"id": "abc",
"number": "NotANumber"
}
the server will simply not initialize the number variable of my TestDTO (value = null) and my response will be the following:
{
"id": "abc"
}
I don't understand... why the server doesn't respond with a "400 - Bad request"? :/
This code is running in Weblogic application server version 12.2.1.1.0 with provided Jersey implementation (I only have one dependency in my pom.xml: javaee-api version 7.0)
Ok so... not throwing any exception is the default choice of MOXy, the new default JAX-RS Json Provider for all Oracle products (Glassfish and WebLogic) (instead of Jackson).
That seems strange to me... but yeah we have to deal with it :/
We have to register a ValidationErrorHandler for that purpose.
I found a complete (and working) solution right there:
Custom MOXyJsonProvider in Jersey 2 not working?