JAX-RS #Path in Composed Annotation - java

This one seems relatively straightforward. I'm messing around with composed annotations, and I'm trying to do the following:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Path("")
public #interface TestAnnotation {
#AliasFor(annotation = Path.class, attribute = "value")
String path();
}
This does not work. When I use it like so:
#Path("")
public class MyResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
#TestAnnotation(path = "/things")
public void postIt(Thing myThing) {
// Do various things and then return a Response
}
}
...I receive a 405 in return. If I do this:
// Remove class-level #Path
// #Path("")
public class MyResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
#TestAnnotation(path = "/things")
public void postIt(Thing myThing) {
// Do various things and then return a Response
}
}
...I receive a 404 in return.
There is just something about #Path or the fact that #Path has a required value attribute that results in this just not functioning, and I have no idea how to remedy it.

After further experimentation and research, it would appear that what I am trying to do is literally not possible.
A follow-up attempt was made to utilize Jackson's #JsonView and expose it through my composed annotation via Spring's #AliasFor, and this also failed to function.
I spent some time thinking about how annotations work and considering peeskillet's comments about compilation vs. processing, and I have come to the conclusion that both Jersey and Jackson must use annotation processors that basically just call "Method.isAnnotationPresent()" for detection of relevant annotations. Spring's #AliasFor most likely does not compile nor weave the aliased meta-annotations into the byte-code of the target methods, and thus they are not found by the processors.
My personal solution to this problem was to drop JAX-RS entirely and use Spring's #RequestMapping, which can be aliased in a composed annotation, and to just accept that Jackson's #JsonView simply can't be integrated into a composed annotation.
Obviously, this is not an ideal solution for most people, especially those with large, already established systems. In such situations, it is more likely that the idea of composed annotations will be abandoned long before JAX-RS.
So if anyone has some more insight into the problem, or someone directly from the Spring team wants to chime in, feel free.

Related

Most efficient way to track execution time of methods in REST API? (Java Spring Boot app, Swagger)

I have a Java Spring Boot API (Swagger) that I is having thousands of calls on it daily. I wanted to log the execution times of these methods for analysis. I used Spring AOP (Aspect Orientated Programming) to make a simple interface and concrete class that allows me to annotate my methods with #TrackExecutionTime to see the runtime. I have listed the code for that below. My problem is, that in the logs, I am tracking the method call times, but I have thousands of requests, so I need a way of "tagging" each api call and logging it, so I can follow the flow of each api call. I was thinking of generating a random # or maybe someone here had a better suggestion. So the logging currently looks like this:
"com.mypackage.myclassname.mymethodname. Time taken for Execution is : 100ms"
Also, My first RestController uses Swagger, so I tried to annotate the method with my #TrackTimeExecution, but we are using the swagger-codegen-maven plugin, so it reads the swagger definition yaml file and generates the "CustomerApi" and other classes/interfaces when it compiles. When I tried annotating at the class level per below, the Spring Boot app compiles, but when I run the app locally on port 8080 and try to hit the endpoint I annotated with Postman, nothing happens at all. It's like annotating breaks the swagger codegen or something, so I had to settle with sticking the annotation on the customersService.getCustomers() method. Is this acceptable? I figured I would need to clock the execution from when the Controller first gets hit, but as I said, I coudln't do it this way unless I'm making some dumb mistake, so I had to put it on the next method the controller calls. Does this make my timing of the api call inaccurate since I would need to time in when the app first gets the request by the controller? Would love any input here...
Dumbed dumb implementation of one of my endpoints, basically the same:
#RestController
#TrackExecutionTime // this fails to compile
public class CustomerApiController implements CustomerApi {
#Autowired
public CustomerApiController(ObjectMapper objectMapper, HttpServletRequest request) {
this.objectMapper = objectMapper;
this.request = request;
}
public ResponseEntity<List<Customer>> searchCustomer() {
return new ResponseEntity<List<Customer>>(this.customerService.getCustomers(), HttpStatus.OK);
Class that logs the execution time of any method annotated with "#TrackExecutionTime"
#Aspect
#Component
#Slf4j
#ConditionalOnExpression("${aspect.enabled:true}")
public class ExecutionTimeAdvice {
#Around("#annotation(com.mailshine.springboot.aop.aspectj.advise.TrackExecutionTime)")
public Object executionTime(ProceedingJoinPoint point) throws Throwable {
long startTime = System.currentTimeMillis();
Object object = point.proceed();
long endtime = System.currentTimeMillis();
log.info("Class Name: "+ point.getSignature().getDeclaringTypeName() +". Method Name: "+ point.getSignature().getName() + ". Time taken for Execution is : " + (endtime-startTime) +"ms");
return object;
}
}
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface TrackExecutionTime {
}
To use this annotation over class you have to change target of the annotation to ElementType.TYPE instead of ElementType.METHOD.

Equivalent of javax.ws.rs NameBinding in Micronaut?

I am working on porting an old HTTP server to Micronaut and I am stuck trying to port an authorization filter that used the javax.ws.rs NameBinding annotation to a Micronaut HTTP server filter. 90% of my endpoints/controllers use the NameBinding annotation I have so using the standard Micronaut HTTP server filter would be difficult.
One code smelly thought was to create a filter accepting all api endpoints (ie. #Filter("/**")) and then maybe storing a list of all the paths that don't require authorization and comparing that against the requested path.
Another hack I attempted to was to try and derive the target method with reflections through the request/chain but it seems that target method is held in an #Internal class which leads me to believe I should not be reflecting on the method from a filter. If I was able to reflect on the target method from a filter I could look for my old annotation and filter on that.
In general are there any guiding principles for providing filters to a large subset of controllers/methods excluding a handful, for example an inverse filter pattern (although this would also not be ideal)?
Is there any way in micronaut to manually control the injection of filters?
If you need a fine grained control over your endpoints, I'll go for micronaut AOP
#Documented
#Retention(RUNTIME)
#Target(ElementType.METHOD)
#Around
#Type(AuthenticatedInterceptor.class)
public #interface Authenticated {
}
and the interceptor coresponding
#Singleton
public class AuthenticatedInterceptor implements MethodInterceptor<Object, Object> {
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
final var authHeader = ServerRequestContext.currentRequest()
.map(HttpMessage::getHeaders)
.flatMap(HttpHeaders::getAuthorization)
.orElseThrow(() -> new RuntimeException("no header"));
validate(authHeader);
return context.proceed();
}
}
then you'll have to add #Authenticated on each methods that need to be authenticated.
UPDATE
Micronaut security provides it's own #Secured annotation.

REST annotations injection together with injection of custom XML objects in one request

1)
I'm dealing with similar situation like at How can I pass complex objects as arguments to a RESTful service? , but actually the injection of my custom XML objects if injected all right, IF i do not annotate method parameter with #Form.
This is wrapper request object to injection:
#XmlRootElement(name = "request")
#XmlAccessorType(XmlAccessType.NONE)
#XmlType
#Consumes({"application/xml", "application/json"})
public class TestRequest {
#PathParam(value = "value")
private String value; // this is injected only when #Form param is added to the method parameter definition
#XmlElement(type = Test.class)
private Test test; // this is XML object I want to inject from the REST request
#XmlElement
private String text; // or inject other XML element like this
}
So this would inject me REST parameters (e.g. {value} - #PathParam("value") annotated in TestRequest).
BUT this doesn't unmarshall the XML object Test in wrapper object TestRequested.
#POST
#Path("tests/{value}")
#Consumes("application/xml")
#Produces("application/xml")
public void addTest(**#Form** TestRequest req);
And following definition would inject only the XML object Test but doesn't inject the REST annotations (e.g. {value} from URI):
public void addTest(TestRequest req); // without #Form annotation now
2) I also tried another approach by catching request to custom MessageBodyReader implementation but have been lost in the details where to find code, method or class of JAX-RS or RESTEasy that actually does this parsing/injecting of REST annotations(#PathParam, #QueryParam,...).
I also noticed that when there is #Form annotation in method definition, then custom MessageBodyReader isn't even catched (propably built-in one for REST parameters catches that request and custom reader is then ignored).
I could in this custom message body reader solution somehow call that built-in injection provider but i didn't find suitable documentation and it seems I'm doing something wrong and all can be done more simplier some other way.
To summarise the goal: inject both REST parameters (#PathParam, #QueryParam etc.) and custom XML/JSON objects somehow in one request in ONE wrapper object.
(It works with one wrapper object annotated #Form and the other parameter to be without #Form annotation, but I would like to have all in one wrapper object).
Thank you for any insights or help.
You are mixing JAX-RS and JAXB annotations. That's a bad idea. Use JAX-RS annotations on resource classes and JAXB annotations on represenation classes.

Spring - Abstracting url mapping to handle common url parameters

Assume the following setup:
We have multiple commands mapped to different URLs, each of these with its own body, which we can capture using mappings, like:
#RequestMapping(value = "url1/{param}/command", method = RequestMethod.POST)
#ResponseBody
public Response command1(#PathVariable("param") String param,
#RequestParam(value = urlParam) Param urlParam,
#RequestBody Request request) {
...}
We have several cases where the same parameter repeats in several urls, specifically the URL parameter. Since we have several such variables, today we manually add them to each mapping which is error prone and too verbose.
Is there anyway of routing all mappings through an initial mapping, capturing all those url parameters, and thus remove the clutter from all other mappings?
If you switch from Spring MVC to any JAX-RS framework (e.g. Jersey, Apache Wink), you can use subresources:
#Path("/parent/{id}")
class ParentResource {
#Path("/child1")
Child1Resource getChild() {
....
}
#Path("/child2")
Child2Resource getChild() {
....
}
}
Pay attention that methods with #Path annotations are not annotated with HTTP Methods, so any relevant HTTP request matching the url will propagate into the subresources.
Another suggestion to reduce the error-proning: use constants (public final static String) as parameters both when you create the url and when you use the parameter. This makes it a little bit more verbose, but reduce the error-proning. It can be used both with Spring-MVC and JAX-RS. Don't forget that it's possible to put constants inside the annotation values.
Hope this helps.

Aop Annotation at Spring Controllers Doesn't Work

I have made an annotation for aop. When I use it at any method rather than controller methods it works well. However when I use it at my controller's methods my controller stops working. It starts to give 404 not found error for mappings. I found a similar question here: Spring 3 MVC #Controller with AOP interceptors? but I don' know how to do it. My method at my controller is that:
#WebAuditable // This is my annotation that works at other methods
#Override
#RequestMapping(value = "/ad", method = RequestMethod.POST, headers = "Accept=application/json")
public
#ResponseBody
Cd create(HttpServletResponse response, #RequestBody Cd cd) {
...
}
My interface that my controller implements is that:
public interface BaseController<T> {
public List<T> getAll(HttpServletResponse response);
public T getByName(HttpServletResponse response, String id);
public T create(HttpServletResponse response, T t);
public T update(HttpServletResponse response, T t);
}
Any advices?
PS: #SeanPatrickFloyd says that:
Note When using controller interfaces (e.g. for AOP proxying), make
sure to consistently put all your mapping annotations - such as
#RequestMapping and #SessionAttributes - on the controller interface
rather than on the implementation class
The thing is: controller mapping is done at runtime, and if you use AOP proxies, the proxy objects don't have annotations at runtime, only their interfaces do. I can think of two possible strategies to work around this limitation.
Either annotate the generic interface methods, or (if you don't want to advise all controllers) create a sub-interface per implementation type, explicitly annotating their methods. I know that's a lot of rewritten code and contrary to what AOP is about, but I don't know a better way when sticking with interface based proxies.
Another way would be to switch to CGLib proxies using proxy-target-class="true". That way the proxy classes should (I'm not sure about this) retain the annotations.
Update: annotating your interface should work like this (if it works)
public interface BaseController<T> {
#WebAuditable
public List<T> getAll(HttpServletResponse response);
#WebAuditable
public T getByName(HttpServletResponse response, String id);
#WebAuditable
public T create(HttpServletResponse response, T t);
#WebAuditable
public T update(HttpServletResponse response, T t);
}
Annotating a base class won't work, because JDK proxies don't expose any information that's not backed by interfaces.

Categories