I'm need to access some model info in a custom editor. I've tried to use ModelMap as an initBinder params but I obtain a deny error on runtime.
Any idea?
public void initBinder(WebDataBinder binder, WebRequest request) {
binder.registerCustomEditor(MyData.class, new MyCustomEditor(model));
}
TIA
Ice72
#InitBinder just register the Editors and Validators, the ModelMap which mapping the HTML<form> will be created after the initBinder, before the #RequestMapping method.
You can use the service in you CustomEditor, and format the fields of your ModelMap which will be created.
public class MyCustomEditor extends PropertyEditorSupport {
private YourService service;
public MyCustomEditor(YourService service){
this.service = service;
}
#Override
public void setAsText(String text){
// TODO service can work here
setValue(text);
}
#Override
public String getAsText(){
// TODO service can work here
return (String) getValue();
}
}
#Resource
YourService yourService;
#InitBinder
protected void initBinder(WebDataBinder binder){
binder.registerCustomEditor(MyData.class, "fieldInMyData", new MyCustomEditor(yourService));
}
Related
I am trying to call validator from controller using #Valid annotation, but control is not going to validator and proceeding without validating.
Controller
#Controller
#RequestMapping(value="/event")
public class EventController {
#Autowired
private EventService eventService;
#Autowired
EventValidator eventValidator;
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(eventValidator);
}
#RequestMapping(value="/add_event",method = RequestMethod.POST,produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<AjaxJSONResponse> postAddEventForm(#Valid #RequestPart("event") Event event, MultipartHttpServletRequest request) {
Boolean inserted = eventService.addEvent(event);
String contextPath = request.getContextPath();
String redirectURL = StringUtils.isEmpty(contextPath)?"/event":contextPath+"/event";
return new ResponseEntity<AjaxJSONResponse>(new AjaxJSONResponse(inserted,"Event Added Successfully",redirectURL), HttpStatus.OK);
}
}
Validator
#Component
public class EventValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Event.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Event event = (Event)target;
if (event.getEventName() == null ||!StringUtils.hasText(event.getEventName())) {
errors.rejectValue("eventName", "", "Event Name is empty");
}
}
}
Please help on this.
Thank in advance
I'm trying to add a Filter that creates an object that is then to be used inside a controller in a Spring Boot application.
The idea is to use the Filter as a "centralized" generator of this object - that is request-specific and useful only in a controller.
I've tried to use the HttpServletRequest request.getSession().setAttribute method: I can access my object in the controller, but then it will be (clearly) added to the session.
Are the Filters the right way to do so? If yes, where can I keep the temporary object generated by the filter to be used by the controllers?
Why Don't you use a Bean with the #Scope('request')
#Component
#Scope(value="request", proxyMode= ScopedProxyMode.TARGET_CLASS)
class UserInfo {
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private String password;
}
and then you can Autowireed this bean in both filter and controller to do setting and getting of data.
lifecycle of this UserInfo bean is only exisits within the request so once the http request is done then it terminates the instance as well
you can use ServletRequest.setAttribute(String name, Object o);
for example
#RestController
#EnableAutoConfiguration
public class App {
#RequestMapping("/")
public String index(HttpServletRequest httpServletRequest) {
return (String) httpServletRequest.getAttribute(MyFilter.passKey);
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#Component
public static class MyFilter implements Filter {
public static String passKey = "passKey";
private static String passValue = "hello world";
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setAttribute(passKey, passValue);
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}
}
An addition to wcong's answer.
Since Spring 4.3 after setting the attribute by using request.setAttribute(passKey, passValue);, you can access the attribute in your controller by simply annotating it with #RequestAttribute.
ex.
#RequestMapping("/")
public String index(#RequestAttribute passKey) {
return (String) passKey;
}
I dont know actually what is the scenario but If you really want to create an object in a filter and then use it somewhere in the code then you may use ThreadLocal class to do so.
To get know how this work see the most voted answer from that question Purpose of ThreadLocal?
In general using ThreadLocal you will be able to create a class that can store objects available ONLY for the current thread.
Sometimes for optimization reasons the same thread can be used to serve subsequent request as well so it will be nice to clean the threadLocal value after the request is processed.
class MyObjectStorage {
static private ThreadLocal threadLocal = new ThreadLocal<MyObject>();
static ThreadLocal<MyObject> getThreadLocal() {
return threadLocal;
}
}
in the filter
MyObjectStorage.getThreadLocal().set(myObject);
and in the Controller
MyObjectStorage.getThreadLocal().get();
Instead of filter you can use also #ControllerAdvice and pass objects to specified Controllers by using model.
#ControllerAdvice(assignableTypes={MyController.class})
class AddMyObjectAdvice {
// if you need request parameters
private #Inject HttpServletRequest request;
#ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("myObject", myObject);
}
}
#Controller
public class MyController{
#RequestMapping(value = "/anyMethod", method = RequestMethod.POST)
public String anyMethod(Model model) {
MyObjecte myObject = model.getAttribute("myObject");
return "result";
}
}
MethodSecurityConfig.java
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled=true)
#ComponentScan(basePackageClasses={EventWritePermissionEvaluator.class})
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{
private EventWritePermissionEvaluator eventWritePermissionEvaluator;
#Autowired
public void setEventWritePermissionEvaluator(
EventWritePermissionEvaluator eventWritePermissionEvaluator) {
this.eventWritePermissionEvaluator = eventWritePermissionEvaluator;
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler=new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(eventWritePermissionEvaluator);
return expressionHandler;
}
}
CustomPermissionEvaluator
#Component
public class EventWritePermissionEvaluator implements PermissionEvaluator{
private ChecklistService checklistService;
private UserService userService;
#Autowired
public void setChecklistService(ChecklistService checklistService) {
this.checklistService = checklistService;
}
#Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public CustomUser currentUser()
{
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
CustomUser customUser=(CustomUser) userService.loadUserByUsername(auth.getName());
return customUser;
}
#Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject, Object permission) {
Checklist checklist=(Checklist) targetDomainObject;
Event event=checklistService.getChecklist(checklist.getId()).getEvent();
String grp=event.getCreator().getGrp();
System.out.println("event grp:"+grp);
System.out.println("user grp:"+currentUser().getGrp());
if(currentUser().getGrp().equals(grp))
return true;
else
return false;
}
#Override
public boolean hasPermission(Authentication authentication,
Serializable targetId, String targetType, Object permission) {
return true;
}
}
ServiceMethod
#PreAuthorize("hasPermission(#ch,'write')")
public Map<String, Object> updateState(Checklist ch, HttpServletRequest request, HttpServletResponse response) throws MessagingException
{
}
The hasPermission() methods which i wrote in permissionEvaluator class are not getting invoked for incoming requests to service layer. Did i wrote anything wrong? i wrote some console statements in hasPermission() methods to see their execution. but i did not see anything in the console.
Thanks
What are you trying to achieve? It seems like I can achieve exactly the same thing just by using UserDetailsService implementation from latest Spring Security.
This is my blog post about it
Implementing UserDetailsService:
http://www.yjsblog.com/2015/10/05/how-to-implement-custom-spring-security-authentication-with-userdetailsservice/
Implementing Role as property on an entity:
http://www.yjsblog.com/2015/10/05/userdetails-role-from-database-or-as-an-entity-property/
Please have a look at above links.
Cheers,
I would like to create own class that will transform HTTP request and initializes object from this HTTP request in my Spring MVC application. I can create object by defining parameters in method but I need to do mapping in my own way and do it manually.
How can I do it with my own implementation that will pass to Spring and it will use it seamlessly?
Update1
Solution that kindly provided Bohuslav Burghardt doesn't work:
HTTP Status 500 - Request processing failed; nested exception is
java.lang.IllegalStateException: An Errors/BindingResult argument is
expected to be declared immediately after the model attribute, the
#RequestBody or the #RequestPart arguments to which they apply: public
java.lang.String
cz.deriva.derivis.api.oauth2.provider.controllers.OAuthController.authorize(api.oauth2.provider.domain.AuthorizationRequest,org.springframework.ui.Model,org.springframework.validation.BindingResult,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
Maybe I should mention that I use own validator:
public class RequestValidator {
public boolean supports(Class clazz) {
return AuthorizationRequest.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
AuthorizationRequest request = (AuthorizationRequest) obj;
if ("foobar".equals(request.getClientId())) {
e.reject("clientId", "nomatch");
}
}
}
and declaration of my method in controller (please not there is needed a validation - #Valid):
#RequestMapping(value = "/authorize", method = {RequestMethod.GET, RequestMethod.POST})
public String authorize(
#Valid AuthorizationRequest authorizationRequest,
BindingResult result
) {
}
I have two configurations classes in my application.
#Configuration
#EnableAutoConfiguration
#EnableWebMvc
#PropertySource("classpath:/jdbc.properties")
public class ApplicationConfig {
}
and
#Configuration
#EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestArgumentResolver());
}
}
What is wrong?
Update 2
The problem is with param BindingResult result, when I remove it it works. But I need the result to process it when some errors occur.
If I understand your requirements correctly, you could implement custom HandlerMethodArgumentResolver for that purpose. See example below for implementation details:
Model object
public class AuthorizationRequestHolder {
#Valid
private AuthorizationRequest authorizationRequest;
private BindingResult bindingResult;
// Constructors, accessors omitted
}
Resolver
public class AuthorizationRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return AuthorizationRequestHolder.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
// Map the authorization request
AuthorizationRequest authRequest = mapFromServletRequest(request);
AuthorizationRequestHolder authRequestHolder = new AuthorizationRequestHolder(authRequest);
// Validate the request
if (parameter.hasParameterAnnotation(Valid.class)) {
WebDataBinder binder = binderFactory.createBinder(webRequest, authRequestHolder, parameter.getParameterName());
binder.validate();
authRequestHolder.setBindingResult(binder.getBindingResult());
}
return authRequestHolder;
}
}
Configuration
#Configuration
#EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestMethodArgumentResolver());
}
}
Usage
#RequestMapping("/auth")
public void doSomething(#Valid AuthRequestHolder authRequestHolder) {
if (authRequestHolder.getBindingResult().hasErrors()) {
// Process errors
}
AuthorizationRequest authRequest = authRequestHolder.getAuthRequest();
// Do something with the authorization request
}
Edit: Updated answer with workaround to non-supported usage of #Valid with HandlerMethodArgumentResolver parameters.
Since springmvc 3.x now supports jsr303 and old spring style validator, i want to mix them in my sample apps. But there is only one method enabled for a specified controller, is that the limit of spring framework or JSR standard?
Here is my sample code.
User.java, stands for the domain model, uses JSR303 for validation.
public class User{
#Size(max = 16, message = "user loginId max-length is 16")
private String loginId;
//omit getter and setter
}
UserValidator.java, implements the org.springframework.validation.Validator interface to support user validation.
public class UserValidator implements Validator {
private UserService userService;
public boolean supports(Class<?> clazz) {
return User.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
User u = (User) target;
// loginName check for new user
if (u.getUserId() == null && !userService.isLoginIdUnique(u.getLoginId(), null)) {
errors.rejectValue("loginId", "user.loginId.unique", new Object[] { u.getLoginId() }, null);
}
}
#Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
UserController.java, uses InitBinder annotation to inject UserValidator into WebDataBinder.
#Controller("jspUserController")
#RequestMapping("/sys/users")
public class UserController {
private UserValidator userValidator;
#Autowired
public void setUserValidator(UserValidator userValidator) {
this.userValidator = userValidator;
}
/*#InitBinder("user")
public void initBinderUser(WebDataBinder binder) {
binder.setValidator(userValidator);
}*/
#RequestMapping(value = "/save")
public String save(#Valid User user, BindingResult bindingResult, Model model, HttpServletRequest request) {
if (bindingResult.hasErrors()) {
return "/sys/user/edit";
}
userService.saveUser(user);
return "redirect:/sys/users/index";
}
}
If I uncomment the #InitBinder("user") in UserController, the JSR303 validation will be disabled. While the current commented code will use JSR validator to do the validation.
Can anyone give me a workaround to mix them in one controller?
You can ADD your validator instead of SETTING it :
#InitBinder("user")
public void initBinderUser(WebDataBinder binder) {
binder.addValidators(userValidator);
}
This will execute the JSR303 validations first and then your custom validator. No need then to call the validator directly in the save method.
You can use your validator directly and let the global LocalValidatorFactoryBean (JSR-303) do its work as well:
#Controller("jspUserController")
#RequestMapping("/sys/users")
public class UserController {
private UserValidator userValidator;
#Autowired
public void setUserValidator(UserValidator userValidator) {
this.userValidator = userValidator;
}
#RequestMapping(value = "/save")
public String save(#Valid User user, BindingResult bindingResult, Model model, HttpServletRequest request) {
this.userValidator.validate(user, bindingResult);
if (bindingResult.hasErrors()) {
return "/sys/user/edit";
}
userService.saveUser(user);
return "redirect:/sys/users/index";
}
}