2

I have the following bean:

public class TerminalAdmin {

    @Id
    @Column(name = "admin_id", nullable = false, unique = true)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
    @SequenceGenerator(name = "user_id", sequenceName = "user_id")
    private Long adminId;

    @Column(name = "email", nullable = false)
    private String email;

    @Column(name = "phone")
    @Size(max = 255)
    private String phone;

    @Size(max = 255)
    @Column(name = "name")
    private String name;

    @Column(name = "registration_date")
    @Temporal(TemporalType.TIMESTAMP)
    private Calendar createDate;

    @Column(name = "password", nullable = false)
    @Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
    private String password;

    @ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
    @JoinTable(name = "admin_role", joinColumns = { 
            @JoinColumn(name = "admin_id", nullable = false) }, 
            inverseJoinColumns = { @JoinColumn(name = "role_id", 
                    nullable = false) })
    private Set<AdminRole> adminRoles;

    @Column(name = "blocked")
    private boolean blocked;
    ...
}

and this:

public class AdminRole {    

    @Id
    @Column(name = "role_id", nullable = false, unique = true)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
    @SequenceGenerator(name = "user_id", sequenceName = "user_id")
    private Long id;

    @Column(name = "role")
    private String role;
    ....
}

Inside controller:

@RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(@Valid TerminalAdmin terminalAdmin,
            BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {

from client side I send following request:

enter image description here

terminalAdmin comes to the method looks like this

enter image description here

  1. Why spring writes values into role field?
  2. How to force spring write 250/251 into id field?

P.S.

I tried to write

InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(AdminRole.class, new PropertyEditorSupport() {
        public void setAsText(String name) {
            ....
        }
    });
}

but setAsText method doesn't invoke.

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
  • You left out some important information. Spring doesn't randomly assign values to fields. Maybe there's a converter or something similar for `AdminRole`. – a better oliver Oct 31 '15 at 11:21
  • @zeroflagL Actually this code was written some times ago. Where can I find something like this ? – gstackoverflow Oct 31 '15 at 11:22
  • Unfortunately there is more than one way to convert data or customize the binding. The only reliable way is to search for references to `AdminRole`. Maybe `AdminRole` has a constructor expecting a `String`. – a better oliver Oct 31 '15 at 11:33
  • @zeroflagL after removing constructor - all works good – gstackoverflow Oct 31 '15 at 12:25

1 Answers1

0

This is not a good practice to populate model objects into to forms since Spring can bind fields to object even if they are not populated into the view if your init binder is not properly configured.

Easiest way is to create DTO objects, eg. you could create AdminTerminalDTO or AdminTerminalForm wich you populate to the view.

The Form could contain same fields as AdminTerminal excluding ID field or any other sensitive fields. You cant insert new ID's from the view since it can cause DB integrity errors.

After successful validation you just persist your model object filling it with DTO/Form Object.

Moreover your JSR-303 Annotations seem to be not used in a proper way.

The @Size Annotation is not proper a validation to check String length. You have to use @Length instead. You use @Size to check length of an arrays. @Size also works on Strings but @Length is more accurate.

  1. You can't just send an Integer and just try to bind to your Set(spring does some weird binding as you can see now) . Instead you already done addNewAdmin method in your controller wich already informs that it adds an Admin User.
  2. You have to assign admin role on the server side right in this method. First you can use DTO wich will contain eg. username,password and other fields. You annote them with proper JSR-303 Annotations. Using bindingResult you check if there were any validation errors. If form is validated fine, you just convert your DTO/Form object to Model object. Then you can add admin role and persist your model object.

I can write some example code if this tips are not enough.

EDIT:

public class TerminalAdminDTO {

    private String username;

    @Length(max = 255)
    public String getUsername(){
        return username;
    }

    public void setUsername(String username){
        this.username = username;
    }

    public TerminalAdmin convertToTerminalAdmin(){
        TerminalAdmin terminalAdmin = new TerminalAdmin();
        terminalAdmin.setUsername(this.username);
        return terminAdmin;
    }

}


@Entity
@Table
public class TerminalAdmin {

    @Id
    @Column(name = "admin_id", nullable = false, unique = true)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
    @SequenceGenerator(name = "user_id", sequenceName = "user_id")
    private Long adminId;

    @Column(name = "email", nullable = false)
    private String email;

    @Column(name = "phone")
    @Size(max = 255)
    private String phone;

    @Size(max = 255)
    @Column(name = "name")
    private String name;

    @Column(name = "registration_date")
    @Temporal(TemporalType.TIMESTAMP)
    private Calendar createDate;

    @Column(name = "password", nullable = false)
    @Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
    private String password;

    @ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
    @JoinTable(name = "admin_role", joinColumns = { 
            @JoinColumn(name = "admin_id", nullable = false) }, 
            inverseJoinColumns = { @JoinColumn(name = "role_id", 
                    nullable = false) })
    private Set<AdminRole> adminRoles;

    @Column(name = "blocked")
    private boolean blocked;
    ...
}


@RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(@Valid TerminalAdminDTO terminalAdminDTO,
            BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
                    if(result.hasErrors()){
                        return "errorPage";
                    }else{
                        userService.createAdminUser(terminalAdminDTO);
                        return "successPage";
                    }
            }


@Service
@Transactional
public class UserServiceImpl implements UserService {

    private final int ADMIN_ROLE_ID = 0;

    @Autowired
    EntityManager entityManager;

    public void createAdminUser(TerminalAdminDTO terminalAdminDTO){
        TerminalAdmin terminalAdmin = terminalAdminDTO.convertToTerminalAdmin();
        AdminRole adminRole = entityManager.find(AdminRole.class,ADMIN_ROLE_ID);
        terminalAdmin.getAdminRoles().add(adminRole);
        entityManager.create(terminalAdmin);
    }

}

I wrote it as an example of way doing it, this is not a ready-made code

Oskar Dajnowicz
  • 1,700
  • 12
  • 16
  • please provide sample – gstackoverflow Oct 31 '15 at 11:09
  • _"You cant insert new ID's from the view since it can cause DB integrity errors"_ - That's why we have constraints. But more importantly: No ID would be inserted in the OP's case. `@Length` is not a standard annotation. You cannot even use it if you don't use the Hibernate validator. _"You can't just send an Integer and just try to bind to your Set"_ - Yes you can, if you know how to do it. _"You have to assign admin role on the server side"_ - Binding occurs on the server side, doesn't it? – a better oliver Oct 31 '15 at 11:29
  • _You cannot even use it if you don't use the Hibernate validator. "You can't just send an Integer and just try to bind to your Set" - Yes you can, if you know how to do it_ following this advice - everything can be done if you know how to do it, sending integer like this can cause a serious security leak, you can add any role this way without a proper validation _"You have to assign admin role on the server side" - Binding occurs on the server side, doesn't it?_ Does it? it binds EVERYTHING not an Admin Role About constraint thing why would you even try constraint new object id? – Oskar Dajnowicz Oct 31 '15 at 11:50
  • If you don't refer to me by name I don't get notified and never will know you replied to my comment. **Every** input is a potential threat, that's why we treat it accordingly. If sending IDs can cause security problems, then the application is insecure. Period. We can assume that in the OP's case `AdminRole` refers to predefined roles. A constraint would prevent a reference to a non-existing role. – a better oliver Oct 31 '15 at 14:34
  • @zeroflagL well i am discussing this concrete situation and your tips are fine in general but here these tips arent very useful. 1) its considered a good practice not to bind a model objects directly to the forms 2) while creating DTO you dont need to create additional constraints 3) if the method is already named addAdminRole why should you add role IDs directly into the view where you can add some different ID 4) i dont like the whole structure of implementating this problem, instead AdminRole there should be just a Role and UserRole wich would be ManyToMany association, its more clear – Oskar Dajnowicz Oct 31 '15 at 17:34
  • @zeroflagL 6) and finally i think we missunderstood because in general it is OK to use IDs as foreign keys in the forms, i agree with that but here i mean that you shouldnt be allowed to insert ID for a newly created object and here because of not proper init binding you can just add Id parameter in the posted data and spring will bind it to it because form is using entity directly – Oskar Dajnowicz Oct 31 '15 at 17:37