How to bind request params without setters? - java

I have a simple controller with a GET handler that accepts an object to bind request parameters:
#RestController
#RequestMapping("/test")
public class SampleController {
#GetMapping
public SomeResponse find(RequestParams params) {
// some code
}
}
The RequestParams is a simple POJO class:
public class RequestParams {
private String param1;
private String param2;
// constructor, getter, and setters
}
Everthing works fine, but I would like to get rid of the setters to make the object immutable to public use. In the documentation for #RequestMapping handler method up to Spring 5.0.2, we read that
possible valid method arguments are:
Command or form objects to bind request parameters to bean properties
(via setters) or directly to fields
Is it possible to somehow override the default Spring Boot configuration so that request parameters are bound to class properties using reflection and not with setters?
Update 2018
In the later versions of Spring's documentation, the quoted statement has been rephrased and no longer contain information about binding request parameters directly to fields.

In addition to JSON annotations suggested by #jihor you can try to use custom Web Data binder, adding following code to your controller or to Controller Advice class to span functionality across multiple controllers.
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.initDirectFieldAccess();
}

Spring Boot libraries depend on Jackson (spring-boot-starter-web<-spring-boot-starter-json<-jackson libraries), so one can use its annotations to control json bindings.
#JsonCreator-annotated constructors or static methods allow to instantiate objects without explicit setters:
#JsonCreator
public RequestParams(#JsonProperty("param1") String param1,
#JsonProperty("param2") String param2) {
this.param1 = param1;
this.param2 = param2;
}
Documentation
https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonCreator.html
https://github.com/FasterXML/jackson-annotations/wiki

Related

How does Spring inject values into variable annotated by #HeaderParam?

I am trying to dive deep into the mechanics of the Spring Framework. The following piece of code achieves inserting "token" parameter from HTTP request into the token variable:
#POST
#Path("/")
Response saveOne(#HeaderParam("token") String token, UserDTO uDTO) {
}
How can I achieve the same?
And what tools do I need? Is it done using AOP? Say, I would like to come up with a custom annotation #MyAnnotation and any parameter or field marked by it would be customized by some logic. Example:
public class MyClass {
...
#MyAnnotation
private String myVar1;
public void myMethod(#MyAnnotation myParam1) {
...
}
...
}
When I create instance of MyClass or when I autowire it in my Spring application I would like to have a piece code that triggers right before I use the myVar1 variable and/or when I call the myMethod(param1). I want to set the annotated variable to whatever value I want. How can I do this?
What makes me wonder is how Spring does do it in case of the #HeaderParam? I basically need the same functionality.

Can I use spring #PreAuthhorize annotation on any method

I started working on an existing spring boot application with spring security.
We have our resources authorized using with the #PreAuthorize annotation
I now have a new use case where I need to apply authorization when updating a collection within the resource
I mean. The logged in user may have permissions to update an entity, but he may not have access to update a collection within the entity
I read that enabling global method security I can put the authorization annotation on any method, so I tried putting it on a method that does the collection update. But it didn't work
Can I really use this annotation anywhere or should it be used only on controller bound methods?
I guess the question is whether this annotation is processed as a filter on a request or is it AOP?
I use it as follows:
#Value
private class FooDto {
int id;
String property1;
Collection<Bar> bars;
}
#PreAuthorize("#id, 'FOO_RESOURCE:WRITE'")
public FooDto update(int id, FooDto dto) {
Foo foo = fooRepository.findBy(id);
updateBars(dto.getBars(), foo.getBars());
return updatedEntityDto;
}
#PreAuthorize("#id, 'BARS:WRITE'")
private void updateBars(Collection<Bar> dtoBars, Collection<Bar> entityBars) {
// update the collection
}

HK2 and Impls with constructor arguments

I'm using HK2 as part of a Jersey RESTful API. I'm working in an multitenant system, and in most of my API calls, the tenant is a path parameter. I also have several DAOs, which currently accept the tenantId in their constructor, such as:
public final class WidgetMapper {
public WidgetMapper(final int tenantId) { .. }
..
}
I'd like to use HK2 to provide my DAOs to the other layers of my application. What's the right way to do this?
Change the DAOs to use a setter rather than a constructor argument. Only .. ick. The tenantId is part of the required state of the DAO.
Add a layer of abstraction. Create <interface>MapperFactory and MapperFactoryImpl, which has a no-arg constructor and a bunch of getters, such as getWidgetMapper and getGizmoMapper. Only .. this seems cumbersome. I'd rather not have to maintain these extra classes.
Is there some magic way for HK2 to inject that int value into the WidgetMapper constructor at runtime? Then I could inject the tenantId into the mapper, and the mapper into my other classes.
?? Other HK2 magic?
You need to extract the tenant ID from the path parameter in the request, so as long as it's OK to instantiate your DAOs for each request, you can implement a Factory.
public WidgetMapperFactory implements Factory<WidgetMapper> {
private final ContainerRequestContext containerRequestContext;
#Inject
public WidgetMapperFactory(ContainerRequestContext containerRequestContext) {
this.containerRequestContext = containerRequestContext;
}
public WidgetMapper provide() {
UriInfo uriInfo = containerRequestContext.getUriInfo();
List<String> matchedUris = uriInfo.getMatchedURIs();
int tenantId = 1; // Actually work it out from the matched URIs
return new WidgetMapper(tenantId);
}
public void dispose() {
// Do any clean up you need
}
}
Then bind the factory:
public MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(WidgetMapperFactory.class).to(WidgetMapper.class).in(RequestScoped.class);
}
});
}
}
You can then inject WidgetMapper in a Resource class, and the WidgetMapper doesn't have any knowledge it's being used in a web service.
Change the DAOs to use a setter rather than a constructor argument.
Only .. ick. The tenantId is part of the required state of the DAO.
If your DAOs are singletons I don't see how this would work (or at least how it could be done cleanly).
What's the right way to do this?
IMO, I think the best approach is to have 1) singleton DAOs 2) some type of proxy that got injected into the DAOs when they were instantiated by HK2 and then provided the correct tenant id for the current thread.
I can think of two ways to do this:
Option 1:
I haven't tried it, but I think you could probably inject UriInfo into your DAOs, either through the constructor, a private field, or setter. You could extract the tenant id for the current request from the UriInfo instance.
If I were you, I'd create an abstract class for my DAOs that got a UriInfo injected into a private field. I'd then provide a protected method to return the current tenant id from uriInfo.getPathParameters
public abstract class AbstractDao {
// jersey/hk2 provides a proxy that references the current thread-bound request
#Context
private UriInfo info;
protected int getTenantId()
{
// always returns the tenant id for the current request. TODO: add
// logic to handle calls that don't have a tenant id.
return Integer.valueOf(info.getPathParameters.getFirst("tenantId");
}
}
Option 2:
?? Other HK2 magic?
You could write a custom injection resolver.
One more idea...
Option 3:
This one doesn't directly answer your question since it doesn't use HK2 to inject the tenant ID into the DAOs but I think it's worth mentioning.
You could implement your own ContainerRequestFilter that obtained the tenant id and provided it to other components in your app.
By default, Jersey will invoke the filter after it resolves the resource method but before the method is actually invoked. You can obtain a UriInfo from the ContainerRequestContext, get the tenant id path param, then stuff that param into your own thread local variable. You could then reference the thread local within your DAO. Again, I recommend adding a protected method in a base DAO class to encapsulate this logic.
in most of my API calls, the tenant is a path parameter
Optionally, you can use NameBinding to control the behavior described above.
If you wanted to, you could implement option 3 using a regular ServletFilter instead.
Note:
After I wrote this answer, I realized that I assumed you were comfortable extending ResourceConfig, that you knew how to obtain an instance of ServiceLocator, and that you were comfortable with adding your own bindings. If you're not, let me know and I'll edit my answer to provide more details.

Abstract classes and Spring MVC #ModelAttribute/#RequestParam

I have a hierarchy of model classes in my Spring/Hibernate application.
When submitting a POST form to a Spring MVC controller, is there any standard way of specifying the type of the object being submitted, so Spring can instantiate the correct subclass of the type declared in the receiving method's #ModelAttribute or #RequestParam?
For example:
public abstract class Product {...}
public class Album extends Product {...}
public class Single extends Product {...}
//Meanwhile, in the controller...
#RequestMapping("/submit.html")
public ModelAndView addProduct(#ModelAttribute("product") #Valid Product product, BindingResult bindingResult, Model model)
{
...//Do stuff, and get either an Album or Single
}
Jackson can deserialize JSON as a specific subtype using the #JsonTypeInfo annotation. I'm hoping Spring can do the same.
Jackson can deserialize JSON as a specific subtype using the
#JsonTypeInfo annotation. I'm hoping Spring can do the same.
Assuming you use Jackson for type conversion (Spring uses Jackson automatically if it finds it on the classpath and you have <mvc:annotation-driven/> in your XML), then it has nothing to do with Spring. Annotate the types, and Jackson will instantiate the correct classes. Nevertheless, you will have to do instanceof checks in your Spring MVC controller method.
Update after comments:
Have a look at 15.3.2.12 Customizing WebDataBinder initialization. You could use an #InitBinder method that registers an editor based on a request parameter:
#InitBinder
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
String productType = request.getParam("type");
PropertyEditor productEditor;
if("album".equalsIgnoreCase(productType)) {
productEditor = new AlbumEditor();
} else if("album".equalsIgnoreCase(productType))
productEditor = new SingleEditor();
} else {
throw SomeNastyException();
}
binder.registerCustomEditor(Product.class, productEditor);
}

Losing Class Custom Annotation For Proxy Classes

I am using Seam to inject beans to my controller using #In annotation. The injected class has a custom annotation, when calling injectedClass.getClass().getAnnotation(annotationClass) it returns null.
When debug I found that Seam passes a proxy instance so getClass() returns InjectedClass_$$_javassist_seam_5 which doesn't have my custom annotation.
How I can get my custom annotation from the proxy class?
Here's how my classes look like:
#CustomAnnotation(value="myvalue")
#Name("myAnnotatedClass")
public class MyAnnotatedClass extends SuperClass {...}
#Scope(ScopeType.SESSION)
#Name("myController")
public class MyController {
#In("#{myAnnotatedClass}")
private MyAnnotatedClass myAnnotatedClass;
public void actionMethod(){
//call another class which call myAnnotatedClass.getClass().getAnnotation(CustomAnnotation.class)
//then do some reflection for MyAnnotatedClass fields
}
}
Good question.
When you call a method by using Seam, it is intercepted by a proxy. And this one enables #In or #Out-jection. But There is an exception to this rule: it does not work when you call an internal method
So try this code
#Name
public class Service {
#In
private MyAnnotatedClass myAnnotatedClass;
public void myInterceptedMethod() {
// internal method bypass interceptor
// So #In or #Out-jection is not enabled
internalMethod();
}
private void internalMethod() {
System.out.println(myAnnotatedClass.getClass().getAnnotation(annotationClass));
}
}
Added to original answer
You want to retrieve an annotation from your bean. But, because of method interceptor, myAnnotatedClass.getClass() returns a proxy object, not the bean class itself.
For each bean class, Seam creates a Component definition, in which is stored in the application context. The name of the attribute follows this pattern: component name plus .component. So if you have a bean like this one
#Name("myBean")
public class MyBean {
}
Its Componet definition is stored in the attribute myBean.component
So inside your method, you can use
Component myBeanComponentDefinition = (Component) Context.getApplicationContext().get("myBean.component");
Now you can call
myBeanComponentDefinition.getBeanClass().getAnnotation(CustomAnnotation.class);
regards,
If you want less "ComponentDefinition" overbloat, you could also use this which also works for CDI and Spring:
Class.forName(myBean.getClass().getCanonicalName().substring(0,myBean.getClass().getCanonicalName().indexOf("$"))).getAnnotation(MyAnnotation.class)

Categories

Resources