I'm looking for a way to customize the default Spring MVC parameter binding. Take this method as an example:
#RequestMapping(value="/index.html")
public ModelAndView doIndex(#RequestParam String param) {
...
This is easy, when I have just a Stringthat I want to extract from the request. However, I want to populate a more complete object, so that my method looks like this:
#RequestMapping(value="/index.html")
public ModelAndView doIndex(Foo bar) {
...
What I'm looking for is some way to declare a binding like this;
#RequestMapping(value="/index.html")
public ModelAndView doIndex(#FooPopulator Foo bar) {
...
And have some other kind of implementor (determined by the #FooPopulator annotation) that does this:
public void doBind(Foo target, ServletRequest originalRequest) {
target.setX(this.computeStuffBasedOn(originalRequest));
target.sety(y);
}
So far I've found out about the #InitBinderbinder annotaion but I'm unsure whether that's really the right choice for this scenarion.
What's the best way?
It is very easy. You can use Converters (that work like one way PropertyEditors but are stateless).
See chapter 5.5 Spring 3 Type Conversion in Spring reference.
If such an converter is registered once, you do not need any additional information, you can simply use
#RequestMapping(value="/index.html")
public ModelAndView doIndex(#RequestParam Foo param) {
For example a simple converter that load an object by its id:
#Component
#CustomConverter //custom qualifyer
public class BUdToUserConverter implements Converter<String, User> {
#Resource
private UserDao userDao;
#Override
public User convert(String source) {
Integer id = Integer.parse(source);
return this.userDao.getByBusinessId(id);
}
}
A "helper" that registers all Beans with #CustomConverter anntoation
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {
#Resource
#CustomConverter
private List<Converter<?, ?>> customConverter;
#Override
protected void installFormatters(final FormatterRegistry registry) {
super.installFormatters(registry);
for (Converter<?, ?> converter : customConverter) {
registry.addConverter(converter);
}
}
}
How to use it
UserController {
...
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ModelAndView show(#PathVariable("id") User user) {
return new ModelAndView("users/show", "user", user);
}
}
just a quick thank you and the info, that I've found the "correct" solution to the problem. Spring already provides the WebArgumentResolver for this scenario.
http://sergialmar.wordpress.com/2011/03/29/extending-handler-method-argument-resolution-in-spring-mvc/
http://scottfrederick.blogspot.com/2011/03/customizing-spring-3-mvcannotation.html
Related
In my Spring MVC application I have a 'DrawingController' that accepts MultipartHttpRequests from clients.
A user can upload any type of drawing( for the time being only auto cad and bim drawings) from the front end.
There is an interface called 'DrawingService' and two implementations 'BIMDrawingService' and 'CADDrawingService' as follows.
public interface DrawingService{
public String manageUpload();
}
#Component("bimService")
public class BIMDrawingService implements DrawingService{
public String manageUpload() {//}
}
#Component("cadService")
public class CADDrawingService implements DrawingService{
public String manageUpload() {//}
}
public class DrawingController {
#Autowired
#Qualifier("bimService")
private DrawingService bimService;
#Autowired
#Qualifier("cadService")
private DrawingService cadService;
public void setDrawingService(DrawingService bimService) {
this.bimService= bimService;
}
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody String handleFileUpload(MultipartHttpServletRequest request){
//if request type == BIM, then ignore the logic how I differentiate
if bimrequest
bimService.manageupload()
else if cadrequest
cadService.manageupload()
}
I feel this is not the good way to do and my question is how I can inject the services dynamically at run time, even If I add new drawing services later, with the minimal changes I should be able to progress. Please suggest me some best design solution.
I created one factory to decide what best implementation should be returned, based in some conditional check.
// Factory
#Component
public class StoreServiceFactory {
#Autowired
private List<StoreService> storeServices;
public StoreService getService(){
if(isActiveSale){
return storeServices.get("PublicStoreService")
}
return storeServices.get("PrivateStoreService")
}
}
//Service Implementations
#Service
#Qualifier("PublicStoreService")
public class PublicStoreService implements StoreService {
public getStoreBalanceScore(){
Do Stuff....
}
}
#Service
#Qualifier("PrivateStoreService")
public class PrivateStoreService implements StoreService {
public getStoreBalanceScore(){
Do Stuff....
}
}
// Controller
#Autowired
StoreServiceFactory storeServiceFactory;
#Override
public StoreData getStoreBalance(String storeId) {
StoreService storeService = storeServiceFactory.getService();
return simulationService.simulate(sellerId, simulation);
}
Is this approach good? If yes, how can i get my service from an elegant way?
I would like to use only annotations, without configurations.
You should use a map instead of a List and pass a string parameter to the getService method.
public class StoreServiceFactory {
#Autowired
private Map<String,StoreService> storeServices = new HashMap<>();
public StoreService getService(String serviceName){
if(some condition...){
// want to return specific implementation on storeServices map, but using #Qualifier os something else
storeServices.get(serviceName)
}
}
}
You can prepopulate the map with supported implementations. You can then get an appropriate service instance as follows :
// Controller
#Autowired
StoreServiceFactory storeServiceFactory;
#Override
public StoreData getStoreBalance(String storeId) {
StoreService storeService = storeServiceFactory.getService("private");//not sure but you could pass storeId as a parameter to getService
return simulationService.simulate(sellerId, simulation);
}
If you don't like using Strings, you can define an enum for the supported implementations and use that as the key for your map.
You don't need to create a list or map on your code. You can retrieve it directly from Spring context using GenericBeanFactoryAccessor. This has various method to retrieve a specific bean like based on name, annotation etc. You can take a look at javadoc here. This avoids unnecessary complexity.
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/beans/factory/generic/GenericBeanFactoryAccessor.html
UPDATE (17.04.2012): So what I have as result.
root-context.xml:
<context:annotation-config/>
<context:component-scan base-package="com.grsnet.qvs.controller.web"/>
<security:global-method-security pre-post-annotations="enabled" />
<bean id="permissionManager" class="com.grsnet.qvs.auth.PermissionManager"/>
PermissionManager.java
package com.grsnet.qvs.auth;
import com.grsnet.qvs.model.Benutzer;
public class PermissionManager {
public PermissionManager() {}
public boolean hasPermissionU01(Object principal, Integer permissionLevel) {
return ((Benutzer)principal).getPermission().getU_01() >= permissionLevel;
}
}
Controller:
#PreAuthorize("#permissionManager.hasPermissionU01(principal, 1)")
#RequestMapping(value = "/u01", method = RequestMethod.GET)
public String listU01(HttpServletRequest request, Map<String, Object> map) throws Exception {
setGridFilters(map);
return "u01panel";
}
I set break point in PermissionManager.hasPermissionU01. it seems my security annotation just ignored.
What is the reason? Where is my mistake?
Thanks.
END OF UPDATE
After hours of googling I have to ask here.
I have
Spring MVC app
CustomUserDetailService
Custom UserDetails class
public class Benutzer extends User implements UserDetails {
...
private Permission permission = null;
...
}
Permissions class, not very good realized, but I have to use it.
public class Permission {
...
private Integer u_01 = 0;
...
}
Controller
#Controller
public class U01Controller {
#RequestMapping(value = "/u01", method = RequestMethod.GET)
public String listU01(HttpServletRequest request, Map<String, Object> map) throws Exception {
My task is to secure the controller at whole and to secure a methods inside.
I would like to write some like this:
#PreAuthorize("principal.permission.u_01>0")
public class U01Controller {
and
#RequestMapping(value = "/u01", method = RequestMethod.GET)
#PreAuthorize("principal.permission.u_01=2")
public String listU01(HttpServletRequest request, Map<String, Object> map) throws Exception {
It seems ACL uses UserDetails interface to gain access to a principal.
Is it probably to make some type cast inside ACL?
#PreAuthorize("(com.grsnet.qvs.model.Benutzer)principal.permission.u_01=2")
Thanks in advance.
While I think you can probably do that (did you just try it?) it seems to me that the best approach would be to create another class that knows how to do permissions decisions. In particular, it could be done like this:
public class Decision {
private Decision() {} // no instance, please
// Type is probably a bit too wide...
static boolean mayList(Object principal) {
return ((com.grsnet.qvs.model.Benutzer)principal).permission.u_01 == 2;
}
// etc...
}
Then your #PreAuthorize can be written like this:
#PreAuthorize("Decision.mayList(principal)")
If the decision process was more complex, then you'd be getting into using a bean to do the decision making. Then, because this is Spring EL, you'd write (assuming you're delegating to to the decider bean):
#PreAuthorize("#decider.mayList(principal)")
(Of course, my little Decider class above definitely isn't a bean…)
prolem was solved with Donal's solution.
My error was I placed
<security:global-method-security pre-post-annotations="enabled" />
in root-context.
Be careful with it and place it in servletContext.
Donal, thanks again.
I have to implement validations for a web app that uses Spring MVC 3. The problem is that the bean class has methods like getProperty("name") and setProperty("name",valueObj). The validations have to be done on the data that is returned by passing different values to getProperty("name") , for eg: getProperty("age") should be greater than 16 and getProperty("state") should be required.
I would like to know if there is any support for validation this kind of Bean and if not, what can be the work around.
Thanks,
Atif
I don't think so. Bean validation is performed on javabeans, i.e. class fields with getters and setters. Even if you can register a custom validator, and make validation work, binding won't work. You would need to also register a custom binder that populates your object. It becomes rather complicated. So stick to the javabeans convention.
It sounds like you want to a custom validation class which implements org.springframework.validation.Validator.
#Component
public class MyValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyBean.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyBean myBean = (MyBean) target;
if (StringUtils.isBlank(myBean.getProperty("state"))) {
errors.rejectValue("state", "blank");
}
}
}
In your controller you would do manual validaton like follows:
#Autowired
private MyValidator myValidator;
#RequestMapping(value = "save", method = RequestMethod.POST)
public String save(#ModelAttribute("myBean") MyBean myBean, BindingResult result) {
myValidator.validate(myBean, result);
if (result.hasErrors()) {
...
}
...
}
Simple and short question: Is there a way to create a handler for custom #RequestParam types in Spring MVC?
I know I can register custom WebArgumentResolvers but then I cannot bind these to parameters. Let me describe my use case:
Consider I have defined a Model Class Account:
public class Account {
private int id;
private String name;
private String email;
}
My request handling method looks as follows:
#RequestMapping("/mycontroller")
public void test(Account account1, Account account2) {
//...
}
If I make a request mydomain.com/mycontroller?account1=23&account2=12 I would like to automatically load the Account objects from the database and return an error if they dont exist.
Yes, you should just register a custom property editor:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(CustomType.class,
new CustomTypePropertyEditor());
}
Update: Since you need to access the DAO, you need the property editor as a spring bean. Something like:
#Component
public class AccountPropertyEditor extends PropertyEditorSupport {
#Inject
private AccountDAO accountDao;
#Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(accountDao.getById(Integer.parseInt(text)));
}
#Override
public String getAsText() {
return String.valueOf(((Account) getValue()).getId());
}
}
And then, when registering the editor, get the editor via injection rather than instantiating it.