CustomMenu

Monday, May 5, 2014

Spring Thymeleaf Roles CRUD - Part 1

In this two part post, we will create CRUD pages for the Role entity.  In this first part we will create the controller, and make some defect fixes in the model and DAO layers.  In order to access this new controller we will need to update the security configuration file also.  Lastly, this post builds on the code created in the series of posts starting with Spring Thymeleaf CRUD User Maintenance - Part 1.


1. Let's start with some updates to the Permission and Strategy entities.  The changes are highlighted in the code fragments below.
...
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null)
            return false;

        if (o instanceof Permission) {
            final Permission other = (Permission) o;
            return Objects.equal(getId(), other.getId())
                    && Objects.equal(getPermissionname(), other.getPermissionname());
        }
        return false;
    }
...
...
public class Strategy extends BaseEntity implements Serializable {

    private static final long serialVersionUID = 96285180113476324L;
    static Logger logger = LoggerFactory.getLogger(Strategy.class);

    @NotNull(message = "{error.strategy.type.null}")
    @NotEmpty(message = "{error.strategy.type.empty}")
    @Size(max = 50, message = "{error.strategy.type.max}")
    @Column(name = "TYPE", length = 50)
    private String type;

    @NotNull(message = "{error.strategy.name.null}")
    @NotEmpty(message = "{error.strategy.name.empty}")
    @Size(max = 50, message = "{error.strategy.name.max}")
    @Column(name = "NAME", length = 50)
    private String name;
...
The defect in the first fragment (Permission.java) was related to an incorrect Object comparison, due to copy-paste. The second change (in Strategy.java) was to increase the field size in both the entity and database DDL.


2. In the DAO layer, I discovered some defects related to the update methods, specifically around an update creating a duplicate entry. Also, I've changed the getList methods on the DAOs to return sorted lists. The relevant updates are in the fragments below.
...
    @Override
    public void updateStrategy(Strategy strategy) throws StrategyNotFoundException, DuplicateStrategyException {
        Strategy strategyToUpdate = getStrategy(strategy.getId());
        
        // iterated to see if this update is a duplicate
        List<Strategy> strategies = getStrategies();
        for (Strategy singleStrategy : strategies) {
            if (singleStrategy.getType().equals(strategy.getType()) && singleStrategy.getName().equals(strategy.getName())) {
                String message = "The Strategy [" + singleStrategy.getName() + "] already exists in the system.";
                throw new DuplicateStrategyException(message);
            }
        }
        
        // no exception thrown, so it is not a duplicate
        strategyToUpdate.setName(strategy.getName());
        strategyToUpdate.setType(strategy.getType());
        getCurrentSession().update(strategyToUpdate);
    }

...

    @Override
    @SuppressWarnings("unchecked")
    public List<Strategy> getStrategies() {
        String hql = "FROM Strategy s ORDER BY s.id";
        return getCurrentSession().createQuery(hql).list();
    }
...
...
@Override
    public void updateUser(User user) throws UserNotFoundException, DuplicateUserException {
        User userToUpdate = getUser(user.getId());
        
        try {
            User userCheck = getUser(user.getUsername());
            if (userCheck.getId() == userToUpdate.getId()) {
                userToUpdate.setEnabled(user.getEnabled());
                userToUpdate.setPassword(user.getPassword());
                userToUpdate.setUsername(user.getUsername());
                userToUpdate.setRole(user.getRole());
                getCurrentSession().update(userToUpdate);
            } else {
                String message = "The user [" + userCheck.getUsername() + "] already exists";
                throw new DuplicateUserException(message);
            }
        } catch (UserNotFoundException e) {
            userToUpdate.setEnabled(user.getEnabled());
            userToUpdate.setPassword(user.getPassword());
            userToUpdate.setUsername(user.getUsername());
            userToUpdate.setRole(user.getRole());
            getCurrentSession().update(userToUpdate);
        }
    }

...

    @Override
    @SuppressWarnings("unchecked")
    public List<User> getUsers() {
        String hql = "FROM User u ORDER BY u.id";
        return getCurrentSession().createQuery(hql).list();
    }
...
...
    @Override
    public void updateRole(Role role) throws RoleNotFoundException, DuplicateRoleException {
        Role roleToUpdate = getRole(role.getId());
        
        try {
            Role roleCheck = getRole(role.getRolename());
            if (roleCheck.getId() == roleToUpdate.getId()) {
                roleToUpdate.setId(role.getId());
                roleToUpdate.setRolename(role.getRolename());
            } else {
                String message = "The role [" + roleCheck.getRolename() + "] already exists";
                throw new DuplicateRoleException(message);
            }
        } catch (RoleNotFoundException e) {
            roleToUpdate.setId(role.getId());
            roleToUpdate.setRolename(role.getRolename());
            getCurrentSession().update(roleToUpdate);
        }
    }

...

    @Override
    @SuppressWarnings("unchecked")
    public List<Role> getRoles() {
        String hql = "FROM Role r ORDER BY r.id";
        return getCurrentSession().createQuery(hql).list();
    }
...
...
    @Override
    public void updatePermission(Permission permission) throws PermissionNotFoundException, DuplicatePermissionException {
        Permission permToUpdate = getPermission(permission.getId());
        
        try {
            Permission permissionCheck = getPermission(permission.getPermissionname());
            if (permToUpdate.getId() == permissionCheck.getId()) {
                permToUpdate.setId(permission.getId());
                permToUpdate.setPermissionname(permission.getPermissionname());
                permToUpdate.setPermRoles(permission.getPermRoles());
                getCurrentSession().update(permToUpdate);
            } else {
                String message = "The permission [" + permissionCheck.getPermissionname() + "] already exists";
                throw new DuplicatePermissionException(message);
            }
        } catch (PermissionNotFoundException e) {
            permToUpdate.setId(permission.getId());
            permToUpdate.setPermissionname(permission.getPermissionname());
            permToUpdate.setPermRoles(permission.getPermRoles());
            getCurrentSession().update(permToUpdate);
        }
    }



    @Override
    @SuppressWarnings("unchecked")
    public List<Permission> getPermissions() {
        String hql = "FROM Permission p ORDER BY p.id";
        return getCurrentSession().createQuery(hql).list();
    }
...

3. Now that we're finished with the refactoring and defects, we will update the security configuration to allow access to the Role controller methods.
package com.dtr.oas.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;

import com.dtr.oas.exception.AccessDeniedExceptionHandler;

@Configuration
@EnableWebMvcSecurity
@ComponentScan(basePackageClasses=com.dtr.oas.service.UserServiceImpl.class)
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    public void configureGlobal(UserDetailsService userDetailsService, AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userDetailsService);
    }
   
    @Autowired
    AccessDeniedExceptionHandler accessDeniedExceptionHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**").permitAll()
                .antMatchers("/error/**").permitAll()
                .antMatchers("/strategy/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("ADMIN")
                .antMatchers("/role/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .permitAll()
                .and()
            .logout()
                .permitAll()
                .and()
            .exceptionHandling()
                .accessDeniedHandler(accessDeniedExceptionHandler);
    }
}

4. We're ready for the Role controller now. I won't discuss this controller, since this one should look pretty similar to the Strategy and User controllers that we created in past posts.
package com.dtr.oas.controller;
import java.util.List;
import java.util.Locale;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.dtr.oas.exception.DuplicateRoleException;
import com.dtr.oas.exception.RoleNotFoundException;
import com.dtr.oas.model.Role;
import com.dtr.oas.service.RoleService;

@Controller
@RequestMapping(value = "/role")
@PreAuthorize("denyAll")
public class RoleController {

    static Logger logger = LoggerFactory.getLogger(RoleController.class);
    static String businessObject = "role"; //used in RedirectAttributes messages 

    @Autowired
    private RoleService roleService;

    @Autowired
    private MessageSource messageSource;
    
    @RequestMapping(value = {"/", "/list"}, method = RequestMethod.GET)
    @PreAuthorize("hasRole('CTRL_ROLE_LIST_GET')")
    public String listOfRoles(Model model) {
        logger.debug("IN: Role/list-GET");

        List<Role> roles = roleService.getRoles();
        model.addAttribute("roles", roles);

        // if there was an error in /add, we do not want to overwrite
        // the existing role object containing the errors.
        if (!model.containsAttribute("role")) {
            logger.debug("Adding Role object to model");
            Role role = new Role();
            model.addAttribute("role", role);
        }
        return "role-list";
    }              
    
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    @PreAuthorize("hasRole('CTRL_ROLE_ADD_POST')")
    public String addRole(@Valid @ModelAttribute Role role,
            BindingResult result, RedirectAttributes redirectAttrs) {

        logger.debug("IN: Role/add-POST");

        if (result.hasErrors()) {
            logger.debug("Role-add error: " + result.toString());
            redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.role", result);
            redirectAttrs.addFlashAttribute("role", role);
            return "redirect:/role/list";
        } else {
            try {
                roleService.addRole(role);
                String message = messageSource.getMessage("ctrl.message.success.add", 
                        new Object[] {businessObject, role.getRolename()}, Locale.US);
                redirectAttrs.addFlashAttribute("message", message);
                return "redirect:/role/list";
            } catch (DuplicateRoleException e) {
                String message = messageSource.getMessage("ctrl.message.error.duplicate", 
                        new Object[] {businessObject, role.getRolename()}, Locale.US);
                redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/role/list";
            }
        }
    }

    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    @PreAuthorize("hasRole('CTRL_ROLE_EDIT_GET')")
    public String editRolePage(@RequestParam(value = "id", required = true) 
            Integer id, Model model, RedirectAttributes redirectAttrs) {
        
        logger.debug("IN: Role/edit-GET:  ID to query = " + id);

        try {
            if (!model.containsAttribute("role")) {
                logger.debug("Adding Role object to model");
                Role role = roleService.getRole(id);
                logger.debug("Role/edit-GET:  " + role.toString());
                model.addAttribute("role", role);
            }
            return "role-edit";
        } catch (RoleNotFoundException e) {
            String message = messageSource.getMessage("ctrl.message.error.notfound", 
                    new Object[] {"role id", id}, Locale.US);
            model.addAttribute("error", message);
            return "redirect:/role/list";
        }
    }
        
    @RequestMapping(value = "/edit", method = RequestMethod.POST)
    @PreAuthorize("hasRole('CTRL_ROLE_EDIT_POST')")
    public String editRole(@Valid @ModelAttribute Role role,
            BindingResult result, RedirectAttributes redirectAttrs,
            @RequestParam(value = "action", required = true) String action) {

        logger.debug("IN: Role/edit-POST: " + action);

        if (action.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
            String message = messageSource.getMessage("ctrl.message.success.cancel", 
                    new Object[] {"Edit", businessObject, role.getRolename()}, Locale.US);
            redirectAttrs.addFlashAttribute("message", message);
        } else if (result.hasErrors()) {
            logger.debug("Role-edit error: " + result.toString());
            redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.role", result);
            redirectAttrs.addFlashAttribute("role", role);
            return "redirect:/role/edit?id=" + role.getId();
        } else if (action.equals(messageSource.getMessage("button.action.save",  null, Locale.US))) {
            logger.debug("Role/edit-POST:  " + role.toString());
            try {
                roleService.updateRole(role);
                String message = messageSource.getMessage("ctrl.message.success.update", 
                        new Object[] {businessObject, role.getRolename()}, Locale.US);
                redirectAttrs.addFlashAttribute("message", message);
            } catch (RoleNotFoundException snf) {
                String message = messageSource.getMessage("ctrl.message.error.notfound", 
                        new Object[] {businessObject, role.getRolename()}, Locale.US);
                redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/role/list";
            } catch (DuplicateRoleException dse) {
                String message = messageSource.getMessage("ctrl.message.error.duplicate", 
                        new Object[] {businessObject, role.getRolename()}, Locale.US);
                redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/role/list";
            }
        }
        return "redirect:/role/list";
    }

    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    @PreAuthorize("hasRole('CTRL_ROLE_DELETE_GET')")
    public String deleteRolePage(
            @RequestParam(value = "id", required = true) Integer id,
            @RequestParam(value = "phase", required = true) String phase,
            Model model, RedirectAttributes redirectAttrs) {

        Role role;
        String message;
        
        try {
            role = roleService.getRole(id);
        } catch (RoleNotFoundException e) {
            message = messageSource.getMessage("ctrl.message.error.notfound", 
                    new Object[] {"Role number", id}, Locale.US);
            redirectAttrs.addFlashAttribute("error", message);
            return "redirect:/role/list";
        }

        logger.debug("IN: Role/delete-GET | id = " + id + " | phase = " + phase + " | " + role.toString());

        if (phase.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
            message = messageSource.getMessage("ctrl.message.success.cancel", 
                    new Object[] {"Delete", businessObject, role.getRolename()}, Locale.US);
            redirectAttrs.addFlashAttribute("message", message);
            return "redirect:/role/list";
        } else if (phase.equals(messageSource.getMessage("button.action.stage", null, Locale.US))) {
            model.addAttribute("role", role);
            return "role-delete";
        } else if (phase.equals(messageSource.getMessage("button.action.delete", null, Locale.US))) {
            try {
                roleService.deleteRole(id);
                message = messageSource.getMessage("ctrl.message.success.delete", 
                        new Object[] {businessObject, role.getRolename()}, Locale.US);
                redirectAttrs.addFlashAttribute("message", message);
                return "redirect:/role/list";
            } catch (RoleNotFoundException e) {
                message = messageSource.getMessage("ctrl.message.error.notfound", 
                        new Object[] {businessObject, role.getRolename()}, Locale.US);
                redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/role/list";
            }
        }
        return "redirect:/role/list";
    }
    
}

In part two we will create the three Thymeleaf html pages that are needed for the Role CRUD functionality.


No comments:

Post a Comment