I would like to check if the user entered the country is in the list of properties.
public class CountryValidator implements ConstraintValidator<CountryValid,String> {
#Value("#{countryOptions}")
Map<String, String> countryOptions;
#Override
public boolean isValid(String girilenDeger, ConstraintValidatorContext arg1) {
// TODO Auto-generated method stub
return countryOptions.containsKey(girilenDeger);
}
#Override
public void initialize(CountryValid constraintAnnotation) {
// TODO Auto-generated method stub
ConstraintValidator.super.initialize(constraintAnnotation);
}
}
However, I have successfully used this list before in the controller class. I get NullPointerException error when I use it again in my validation class.
#Controller#RequestMapping("/customerForm")
public class CustomerController {
#Value("#{countryOptions}")
Map<String, String> countryOptions;
#RequestMapping("/mainMenu")
public String returnForm(Model model) {
model.addAttribute("theCountryOptions", countryOptions);
Customer customer1 = new Customer();
model.addAttribute("customer1", customer1);
return "customer-view/main-menu";
}
#RequestMapping("/resultPage")
public String returnResult(#Valid #ModelAttribute("customer1") Customer customer, BindingResult result,
Model model) {
model.addAttribute("theCountryOptions", countryOptions);
if (result.hasErrors())
return "customer-view/main-menu";
else {
AddDatabase<Customer> database = new AddDatabase<Customer>();
database.setObj(customer);
database.addData();
System.out.println("Ekleme islemi tamamlandı.");
return "customer-view/result-page";
}
}
}
Or can I retrieve theCountryOptions attribute from the model?
Related
I am trying to convert / serialize the #Id field (which is not a string) of the model class but keep getting this error. The custom ID class just concatenates two values with a colon, e.g. aaaa:2345.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [MyIdClass] to type [byte[]]
This is my model class.
#RedisHash("alert")
public class MyClass implements Serializable
{
public static class MyIdClass
{
public String userId;
public Long sessionExpiry;
public MyIdClass()
{
}
public MyIdClass(String id, Long ex)
{
userId = id;
sessionExpiry = ex;
}
}
public static class MyIdClassSerializer implements RedisSerializer<MyIdClass>
{
#Nullable
#Override
public byte[] serialize(#Nullable MyIdClass uid) throws SerializationException
{
return String.format("%s:%d", uid.userId, uid.sessionExpiry).getBytes();
}
#Nullable
#Override
public MyIdClass deserialize(#Nullable byte[] bytes) throws SerializationException
{
String[] t = new String(bytes).split(":");
return new MyIdClass(t[0], Long.parseLong(t[1]));
}
}
#Component
#ReadingConverter
public static class MyIdClassReader implements Converter<byte[], MyIdClass>
{
#Nullable
#Override
public MyIdClass convert(byte[] source)
{
String[] t = new String(source).split(":");
return new MyIdClass(t[0], Long.parseLong(t[1]));
}
}
#Component
#WritingConverter
public static class MyIdClassWriter implements Converter<MyIdClass, byte[]>
{
#Nullable
#Override
public byte[] convert(MyIdClass uid)
{
return String.format("%s:%d", uid.userId, uid.sessionExpiry).getBytes();
}
}
/**
* User ID
* Key := 'alert:' userId ':' sessionExpiry
*/
#Id
public MyIdClass id;
public String value;
}
Here's how I'm configuring the RedisTemplate.
#Bean("redisTemplateActivityAlert")
public RedisTemplate<ActivityAlert.UserIdExpiry, ActivityAlert> redisTemplateActivityAlert()
{
RedisTemplate<ActivityAlert.UserIdExpiry, ActivityAlert> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new ActivityAlert.UserIdExpirySerializer());
template.setHashKeySerializer(new ActivityAlert.UserIdExpirySerializer());
return template;
}
I've read other posts about configuring ConversionService or TypeConverter but haven't gone far with them.
add this Bean to your RedisConfiguration :
#Bean
public RedisCustomConversions redisCustomConversions(MyIdClassReader myIdClassReader , MyIdClassWriter myIdClassWriter ) {
return new RedisCustomConversions(Arrays.asList(myIdClassWriter,myIdClassReader));
}
In my spring mvc web-application i use a generic converter that converts String (id) to Company by fetch using (service and dao) components
first of all in my MVC-config i add the converter like follow :
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new GenericIdToCompanyConverter(new CompanyServiceImp()));
}
companyService
#Service
#Transactional
#Qualifier("companyService")
public class CompanyServiceImp implements ICompanyService {
#Resource
#Qualifier("companyDAO")
private ICompanyDao dao;
public void setDao(ICompanyDao dao) {
this.dao = dao;
}
#Override
public Company find(Long id) throws BusinessException {
Company current = dao.find(id);
if(current == null) {
throw new BusinessException("notFound");
}
return current;
}
....
}
Generic converter :
public class GenericIdToCompanyConverter implements GenericConverter {
private ICompanyService companyService;
public GenericIdToCompanyConverter(ICompanyService companyService) {
super();
this.companyService = companyService;
}
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
ConvertiblePair[] pairs = new ConvertiblePair[] { new ConvertiblePair(Number.class, Company.class), new ConvertiblePair(String.class, Company.class) };
return ImmutableSet.copyOf(pairs);
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
long id = 0;
if( sourceType.getType() == String.class) {
try {
id = Long.valueOf((String) source);
}catch(NumberFormatException e) {
return null;
}
}else if( sourceType.getType() == Number.class) {
id = (Long) source;
}else {
return null;
}
try {
return companyService.find(Long.valueOf(id));
} catch (BusinessException e) {
return null;
}
}
}
and here the controller that receives data form (via ajax request)
public #ResponseBody JsonResponseBean applay(#Valid VoucherForm form, BindingResult result, Locale locale) throws BusinessException {
....
}
where VoucherForm has these attributes
public class VoucherForm{
protected Long id;
protected Company company;
...
}
when i run the application and call controller method it returns type mismatch error for company attribute
and when i execute this on debug mode i see that it fails on serviceCompany - dao.find(id) statment where my dao is == null
Please help
finally i have to autowire the converter
Mvc-config
....
#Autowired
private GenericIdToCompanyConverter genericIdToCompanyConverter;
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(genericIdToCompanyConverter);
}
and update the converter like follow :
public class GenericIdToCompanyConverter implements GenericConverter {
#Resource
#Qualifier("companyService")
private ICompanyService companyService;
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
....
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
....
}
}
I have created an implementation of HandlerMethodArgumentResolver to return the current logged in user for controller methods annotated with #CurrentUser. However, when the resolveArgument method of the HandlerMethodArgumentResolver gets called, an empty user is returned. I have verified that my custom UserDetailsService implementation does retrieve a full user object.
The code I'm using is as follows.
The #CurrUser annotation:
#Target({ElementType.PARAMETER, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#AuthenticationPrincipal
public #interface CurrentUser {
}
The HandlerMethodArgumentResolver implementation with empty user:
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Inject
private UserService userService;
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(CurrentUser.class) != null
&& parameter.getParameterType().equals(User.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
if (this.supportsParameter(parameter)) {
Principal principal = webRequest.getUserPrincipal();
User user = (User) ((Authentication) principal).getPrincipal(); // This user is empty!!!
return user;
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
The calling Controller method:
#RequestMapping(value = "/user", method = RequestMethod.GET)
public HttpEntity<Resource<User>> currentUser(#CurrentUser User self) {
log.debug("CurrentUserController > currentUser GET> " + self);
}
The loadUserByUsername of UserDetailsService that retrieves the populated user (I verified this is called first):
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
CustomUserDetails ud = new CustomUserDetails(user);
return ud;
}
The WebMvcConfigurerAdapter configuration:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(currentUserMethodArgumentResolver());
}
#Bean
public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
return new CurrentUserMethodArgumentResolver();
}
I discovered what the issue was - the problem was in how I implemented `CustomUserDetails':
public static class CustomUserDetails extends User implements UserDetails {
...
}
I was extending my custom User class and implementing UserDetails. I'm not sure why, but Spring didn't like this. I since fixed the code by creating and populating a new org.springframework.security.core.userdetails.User, and returning this from the call to `loadUserByUsername'.
I am creating a webservice in spring. I have a Params DTO which is nested in my OtherParentDTO's. Each request may contain only certain fields in the params Dto.
If the fields are present then I need to do a validation(basically null check). In the custom validator I ll specify which fields needs to be validated for a particular request. My problem is in the controller the error field is returned as params. Is there any way to change it to params.customerId or parmas.userId.
Update customer req:
{"params":{"customerId" : "b2cab997-df13-4cb0-8f67-4357b019bb96"}, "customer":{}}
Update user req:
{"params":{"userId" : "b2cab997-df13-4cb0-8f67-4357b019bb96"}, "user":{}}
#JsonSerialize(include = Inclusion.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Params {
private String customerId;
private String userId;
//setter and getter are there
}
public class UpdateCustomerRequestDTO {
#NotNull
#IsValid(params = {"customerId"})
protected Params params;
#NotNull #Valid
private Customer customer;
}
public class UpdateUserRequestDTO {
#NotNull
#IsValid(params = {"userId"})
protected Params params;
#NotNull #Valid
private User user;
}
Custom constrain validator
#Constraint(validatedBy = {RequestParamsValidator.class})
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface IsValid {
String[] params() default "";
String message() default "{com.test.controller.validator.IsValid.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class RequestParamsValidator implements ConstraintValidator<IsValid, Params> {
/* (non-Javadoc)
* #see javax.validation.ConstraintValidator#initialize(java.lang.annotation.Annotation)
*/
#Override
public void initialize(IsValid constraintAnnotation) {
validateItems = constraintAnnotation.params();
}
/* (non-Javadoc)
* #see javax.validation.ConstraintValidator#isValid(java.lang.Object, javax.validation.ConstraintValidatorContext)
*/
#Override
public boolean isValid(Params value, ConstraintValidatorContext context) {
try {
for (String reqItem : validateItems) {
final Object curObj = PropertyUtils.getProperty(value, reqItem);
if (curObj == null || curObj.toString().isEmpty()) {
return false;
}
}
} catch (final Exception ignore) {
// ignore
}
return true;
}
}
Controller
#RequestMapping(method = RequestMethod.POST, value="", produces="application/json")
public #ResponseBody BaseResponseDTO updateCustomer(#RequestBody #Valid UpdateCustomerRequestDTO requestDTO,
BindingResult result) throws Exception {
if (result.hasErrors()) {
log.error("[Field] "+result.getFieldError().getField()+" [Message]"+ result.getFieldError().getDefaultMessage())
// But here the result.getFieldError().getField() is returning params. Is there any way with which I can change it to params.customerId/parmas.userId
return false
}
// add customer logic
}
I have built a custom component button, but somehow the action is not invoked. When debugging the getAction-Method within the component and invoking the supplied MethodeExpression the Bean-Method is called as expected. But due to some reason, the Expression is not invoked when pressing the button in the browser.
Is there some kind of additional Interface necessary to pass the action to the embedded button-component?
Any help is very appreciated since I am stuck at this issue for some days now
MyClass:
public class MyClass extends UIPanel implements SystemEventListener
{
private UIForm form;
private HtmlCommandButton buttonOk;
public MyClass()
{
FacesContext context = getFacesContext();
UIViewRoot root = context.getViewRoot();
root.subscribeToViewEvent(PostAddToViewEvent.class, this);
}
#Override
public void processEvent(SystemEvent event)
{
this.form = new UIForm();
this.buttonOk = new HtmlCommandButton();
this.buttonOk.setId("okButtonId");
this.buttonOk.setActionExpression(getAction());
this.buttonOk.setValue("OK");
this.form.getChildren().add(this.buttonOk);
getChildren().add(this.form);
}
private enum PropertyKeys
{
action, text, titel
}
public MethodExpression getAction()
{
return (MethodExpression) getStateHelper().eval(PropertyKeys.action);
}
public void setAction(MethodExpression actionExpression)
{
getStateHelper().put(PropertyKeys.action, actionExpression);
}
public String getText()
{
return (String) getStateHelper().eval(PropertyKeys.text);
}
public void setText(String text)
{
getStateHelper().put(PropertyKeys.text, text);
}
public String getTitel()
{
return (String) getStateHelper().eval(PropertyKeys.titel);
}
public void setTitel(String titel)
{
getStateHelper().put(PropertyKeys.titel, titel);
}
#Override
public void encodeAll(FacesContext context) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(HTML.DIV_ELEM, this);
writer.writeText(getText(), null);
this.form.encodeAll(context);
writer.endElement(HTML.DIV_ELEM);
}
#Override
public void encodeChildren(FacesContext context) throws IOException
{
}
#Override
public boolean isListenerForSource(Object source)
{
return (source instanceof MyClass);
}
}
MyClassHandler:
public class MyClassHandler extends ComponentHandler
{
public MyClassHandler(ComponentConfig config)
{
super(config);
}
#SuppressWarnings("rawtypes")
#Override
protected MetaRuleset createMetaRuleset(Class type)
{
return super.createMetaRuleset(type).addRule(new MethodRule("action", String.class, new Class[] { ActionEvent.class }));
}
}
myView Method:
...
public String myMethod()
{
System.err.println("myMethod");
return "/some/path/yadayada.xhtml";
}
...
MyView.xhtml
<myTag action="#{myView.myMethod}" id="id1" titel="bla" text="bleh" />
Exdending UICommand is enough, since you only want one action to be executed.
You have to provide two additional MethodExpressions via the tag-attributes and within the decode-method you can check which button has been pressed and redirect the particular MethodExpression to the standard-action provided by UICommand. This way, you dont have to worry about the legacy-interface ActionSource, or how Events are broadcasted.
public void decode(FacesContext contex)
{
Map<String,String> map = context.getExternalContext.getRequestParameterMap();
// your rendered buttons need a name you check for
final boolean okPressed = map.containsKey( getClientId + ":ok" );
final boolean cancelPressed = map.containsKey( getClientId + ":cancel" );
if(okPressed || cancelPressed)
{
MethodExpression exp = null;
if(okPressed)
{
exp = getActionOk();
}
else
{
exp = getActionCancel();
}
// redirect to standard action
setActionExpression(exp);
queueEvent(new ActionEvent(this));
}
}
In order to make use of of this you need two attributes (actionOk and actionCancel) which use Method Expressions (setter and getter). Those have to be configured by a ComponentHandler as you did for the action-attribute.