From 52af8f39bad0d3ace912a797f2d8168ce95c4739 Mon Sep 17 00:00:00 2001 From: nova Date: Sat, 25 Jan 2020 20:23:05 +0530 Subject: [PATCH] Fixed detached object passed to persist error Error was introduced after adding flyway migration Also made various optimizations to user service and session logging --- chatto/eclipse-formatter.xml | 315 ++++++++++++++++++ .../ros/chatto/config/DataSourceConfig.java | 2 +- .../org/ros/chatto/dto/MessageCipherDTO.java | 6 +- .../org/ros/chatto/logged/LoggedUser.java | 70 +--- .../java/org/ros/chatto/model/UserRole.java | 36 +- .../org/ros/chatto/model/UserSession.java | 3 +- .../java/org/ros/chatto/model/UserToken.java | 2 +- .../security/TokenAuthenticationFilter.java | 19 +- .../org/ros/chatto/service/UserService.java | 3 + .../ros/chatto/service/UserServiceImpl.java | 47 +++ .../ros/chatto/service/UserTokenService.java | 36 +- 11 files changed, 414 insertions(+), 125 deletions(-) create mode 100644 chatto/eclipse-formatter.xml diff --git a/chatto/eclipse-formatter.xml b/chatto/eclipse-formatter.xml new file mode 100644 index 0000000..0f7be6f --- /dev/null +++ b/chatto/eclipse-formatter.xml @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/config/DataSourceConfig.java b/chatto/src/main/java/org/ros/chatto/config/DataSourceConfig.java index 9354e5d..b094ee2 100644 --- a/chatto/src/main/java/org/ros/chatto/config/DataSourceConfig.java +++ b/chatto/src/main/java/org/ros/chatto/config/DataSourceConfig.java @@ -17,7 +17,7 @@ import lombok.extern.slf4j.Slf4j; // @ConfigurationProperties(prefix = "chatto.datasource") @Getter -@Setter +// @Setter @Slf4j public class DataSourceConfig { diff --git a/chatto/src/main/java/org/ros/chatto/dto/MessageCipherDTO.java b/chatto/src/main/java/org/ros/chatto/dto/MessageCipherDTO.java index 80bef33..602338d 100644 --- a/chatto/src/main/java/org/ros/chatto/dto/MessageCipherDTO.java +++ b/chatto/src/main/java/org/ros/chatto/dto/MessageCipherDTO.java @@ -8,11 +8,9 @@ import javax.validation.constraints.Size; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; -@Getter -@Setter +@Data public class MessageCipherDTO { @Pattern(regexp = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") // regex for base64 @NotBlank diff --git a/chatto/src/main/java/org/ros/chatto/logged/LoggedUser.java b/chatto/src/main/java/org/ros/chatto/logged/LoggedUser.java index a5a6a49..3278e0c 100644 --- a/chatto/src/main/java/org/ros/chatto/logged/LoggedUser.java +++ b/chatto/src/main/java/org/ros/chatto/logged/LoggedUser.java @@ -1,101 +1,49 @@ package org.ros.chatto.logged; -import java.time.Instant; - import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; -import org.ros.chatto.model.ChatUser; import org.ros.chatto.model.UserSession; -import org.ros.chatto.repository.UserSessionRepository; import org.ros.chatto.service.UserService; -import org.ros.chatto.service.UserTokenService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.InternalAuthenticationServiceException; -import org.springframework.stereotype.Component; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; -@Component @Getter @Setter +@Slf4j public class LoggedUser implements HttpSessionBindingListener { private final String username; private final UserService userService; - private final UserTokenService userTokenService; - private final Logger logger = LoggerFactory.getLogger(LoggedUser.class); public LoggedUser(String username) { this.username = username; userService = BeanUtil.getBean(UserService.class); - userTokenService = BeanUtil.getBean(UserTokenService.class); - } - - public LoggedUser() { - username = null; - userService = BeanUtil.getBean(UserService.class); - userTokenService = BeanUtil.getBean(UserTokenService.class); } @Override public void valueBound(HttpSessionBindingEvent event) { -// UserService userService = getUserService(event); - UserService userService = BeanUtil.getBean(UserService.class); -// UserSessionRepository userSessionRepository = getUserSessionRepository(event); - UserSessionRepository userSessionRepository = BeanUtil.getBean(UserSessionRepository.class); - LoggedUser user = (LoggedUser) event.getValue(); - Instant instant = Instant.now(); - ChatUser chatUser = userService.findByUserName(user.getUsername()); + LoggedUser user = (LoggedUser) event.getValue(); - UserSession userSession = userSessionRepository.findByUserName(user.getUsername()); + log.debug("Incrementing session count for user {}", user.getUsername()); - if (userSession == null) { - userSession = new UserSession(); - } + UserSession userSession = userService.incrementUserSession(user.getUsername()); - userSession.setUser(chatUser); - userSession.setTimeStamp(instant); - userSession.setOnline(true); - userSession.setNumSessions(userSession.getNumSessions() + 1); - userSessionRepository.save(userSession); + log.trace("Username = {} with sessions = {}", userSession.getUser(), userSession.getNumSessions()); } @Override public void valueUnbound(HttpSessionBindingEvent event) { LoggedUser user = (LoggedUser) event.getValue(); - UserService userService = BeanUtil.getBean(UserService.class); - UserSessionRepository userSessionRepository = BeanUtil.getBean(UserSessionRepository.class); - Instant instant = Instant.now(); - - ChatUser chatUser = userService.findByUserName(user.getUsername()); - - UserSession userSession = userSessionRepository.findByUserName(user.getUsername()); - - if (userSession == null) { -// userSession = new UserSession(); - logger.error("User session is somehow null for user: " + username); - throw new InternalAuthenticationServiceException("User session not found"); - } - - int numSessions = userSession.getNumSessions(); + log.debug("Decrementing session count for user {}", user.getUsername()); - if (--numSessions == 0) { - logger.info("Num sessions is 0 so setting user to offline"); - logger.info("Deleting token and evicting cache for user: " + chatUser.getUserName()); - userSession.setOnline(false); - userTokenService.deleteToken(chatUser.getUserName()); - TokenCacheUtil.evictSingleTokenValue(chatUser.getUserName()); - } + UserSession userSession = userService.decrementUserSession(user.getUsername()); - userSession.setUser(chatUser); - userSession.setTimeStamp(instant); - userSession.setNumSessions(numSessions); - userSessionRepository.save(userSession); + log.trace("Username = {} with sessions = {}", userSession.getUser(), userSession.getNumSessions()); } } \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/model/UserRole.java b/chatto/src/main/java/org/ros/chatto/model/UserRole.java index 5ee284d..ddbd169 100644 --- a/chatto/src/main/java/org/ros/chatto/model/UserRole.java +++ b/chatto/src/main/java/org/ros/chatto/model/UserRole.java @@ -9,44 +9,22 @@ import javax.persistence.Table; import com.fasterxml.jackson.annotation.JsonManagedReference; +import lombok.Data; + @Entity @Table(name = "users_roles") +@Data public class UserRole { @Id private int id; - + @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") + @JoinColumn(name = "user_id") @JsonManagedReference - private ChatUser user; - + private ChatUser user; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "role_id") @JsonManagedReference private Role role; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public ChatUser getUser() { - return user; - } - - public void setUser(ChatUser user) { - this.user = user; - } - - public Role getRole() { - return role; - } - - public void setRole(Role role) { - this.role = role; - } - } diff --git a/chatto/src/main/java/org/ros/chatto/model/UserSession.java b/chatto/src/main/java/org/ros/chatto/model/UserSession.java index 6263d90..7ee073b 100644 --- a/chatto/src/main/java/org/ros/chatto/model/UserSession.java +++ b/chatto/src/main/java/org/ros/chatto/model/UserSession.java @@ -21,7 +21,7 @@ public class UserSession { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; -// private String sessionId; + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private ChatUser user; @@ -29,6 +29,5 @@ public class UserSession { private boolean online; private int numSessions; -// private Boolean loggedIn; private Instant timeStamp; } diff --git a/chatto/src/main/java/org/ros/chatto/model/UserToken.java b/chatto/src/main/java/org/ros/chatto/model/UserToken.java index 5901307..d307cc1 100644 --- a/chatto/src/main/java/org/ros/chatto/model/UserToken.java +++ b/chatto/src/main/java/org/ros/chatto/model/UserToken.java @@ -14,7 +14,7 @@ import lombok.Data; @Data @Entity -@Table(name="tokens") +@Table(name = "tokens") public class UserToken implements Serializable { /** * diff --git a/chatto/src/main/java/org/ros/chatto/security/TokenAuthenticationFilter.java b/chatto/src/main/java/org/ros/chatto/security/TokenAuthenticationFilter.java index 6b5e9dc..7c80c63 100644 --- a/chatto/src/main/java/org/ros/chatto/security/TokenAuthenticationFilter.java +++ b/chatto/src/main/java/org/ros/chatto/security/TokenAuthenticationFilter.java @@ -33,17 +33,20 @@ import org.springframework.web.filter.OncePerRequestFilter; public class TokenAuthenticationFilter extends OncePerRequestFilter { @Autowired - private UserTokenService userTokenService; + private final UserTokenService userTokenService; @Autowired - private TokenService tokenService; + private final TokenService tokenService; private final Logger logger = LoggerFactory.getLogger(TokenAuthenticationFilter.class); private final int tokenTimeoutDuration; - public TokenAuthenticationFilter(@Value("${chatto.token.timeout-duration}") String tokenTimeoutDuration) { + public TokenAuthenticationFilter(UserTokenService userTokenService, TokenService tokenService, + @Value("${chatto.token.timeout-duration}") String tokenTimeoutDuration) { this.tokenTimeoutDuration = Integer.parseInt(tokenTimeoutDuration); + this.userTokenService = userTokenService; + this.tokenService = tokenService; } private boolean isTokenExpired(UserToken userToken) { @@ -81,7 +84,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { } boolean isTokenExpired = isTokenExpired(userToken); - logger.info(String.format("Token for %s is expired? %s", userToken.getUserName(), isTokenExpired)); + logger.trace(String.format("Token for %s is expired? %s", userName, isTokenExpired)); if (!isTokenExpired) { userToken.setCreationTime(Instant.now()); userTokenService.saveToken(userToken); @@ -92,14 +95,14 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { userName, token.getKey(), updatedAuthorities); SecurityContextHolder.getContext().setAuthentication(authentication); } else { - userTokenService.deleteToken(userToken.getUserName()); + userTokenService.deleteToken(userToken); TokenCacheUtil.evictSingleTokenValue(userToken.getTokenContent()); response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE); response.setStatus(440); -// response.sendError(440, "Token authentication error: Token has expired"); + // response.sendError(440, "Token authentication error: Token has expired"); response.getWriter().write("Token authentication error: Token has expired"); logger.warn("Token authentication error: Token has expired"); -// return; + // return; } } @@ -110,7 +113,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { catch (BadCredentialsException e) { response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); -// response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); + // response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); response.getWriter().write("Token authentication error"); logger.warn("Token authentication error: " + e.getMessage()); } diff --git a/chatto/src/main/java/org/ros/chatto/service/UserService.java b/chatto/src/main/java/org/ros/chatto/service/UserService.java index d849ff0..03252fe 100644 --- a/chatto/src/main/java/org/ros/chatto/service/UserService.java +++ b/chatto/src/main/java/org/ros/chatto/service/UserService.java @@ -6,6 +6,7 @@ import org.ros.chatto.dto.ActiveUserDTO; import org.ros.chatto.dto.UserRegistrationDTO; import org.ros.chatto.model.ChatUser; import org.ros.chatto.model.UserRole; +import org.ros.chatto.model.UserSession; import org.springframework.stereotype.Service; @Service @@ -16,4 +17,6 @@ public interface UserService { public ChatUser findByUserName(String userName); public List getOtherActiveUsers(String userName); public List getUserWithRole(String userName); + public UserSession incrementUserSession(String userName); + public UserSession decrementUserSession(String userName); } diff --git a/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java b/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java index 7c507fe..cf50b94 100644 --- a/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java +++ b/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java @@ -9,6 +9,7 @@ import java.util.Map; import org.ros.chatto.dto.ActiveUserDTO; import org.ros.chatto.dto.UserRegistrationDTO; +import org.ros.chatto.logged.TokenCacheUtil; import org.ros.chatto.model.ChatUser; import org.ros.chatto.model.Role; import org.ros.chatto.model.UserRole; @@ -17,15 +18,18 @@ import org.ros.chatto.repository.RoleRepository; import org.ros.chatto.repository.UserRepository; import org.ros.chatto.repository.UserRoleRepository; import org.ros.chatto.repository.UserSessionRepository; +import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Transactional @Service @RequiredArgsConstructor +@Slf4j public class UserServiceImpl implements UserService { private final UserRepository userRepository; @@ -37,6 +41,8 @@ public class UserServiceImpl implements UserService { private final UserSessionRepository userSessionRepository; + private final UserTokenService userTokenService; + @Override public UserRole registerUser(final UserRegistrationDTO userRegistrationDTO) { final ChatUser user = new ChatUser(); @@ -145,4 +151,45 @@ public class UserServiceImpl implements UserService { public List getUserWithRole(final String userName) { return userRoleRepository.findByUser(userName); } + + @Override + public UserSession incrementUserSession(String userName) { + ChatUser chatUser = findByUserName(userName); + + UserSession userSession = userSessionRepository.findByUserName(userName); + + if (userSession == null) { + userSession = new UserSession(); + } + + userSession.setUser(chatUser); + userSession.setOnline(true); + userSession.setNumSessions(userSession.getNumSessions() + 1); + return userSessionRepository.save(userSession); + } + + @Override + public UserSession decrementUserSession(String userName) { + UserSession userSession = userSessionRepository.findByUserName(userName); + + if (userSession == null) { + log.error("User session is somehow null for user: " + userName); + throw new InternalAuthenticationServiceException("User session not found"); + } + + ChatUser chatUser = userSession.getUser(); + + int numSessions = userSession.getNumSessions(); + + if (--numSessions == 0) { + log.info("Num sessions is 0 so setting user to offline"); + log.info("Deleting token and evicting cache for user: " + chatUser.getUserName()); + userSession.setOnline(false); + userTokenService.deleteToken(chatUser.getUserName()); + TokenCacheUtil.evictSingleTokenValue(chatUser.getUserName()); + } + + userSession.setNumSessions(numSessions); + return userSessionRepository.save(userSession); + } } diff --git a/chatto/src/main/java/org/ros/chatto/service/UserTokenService.java b/chatto/src/main/java/org/ros/chatto/service/UserTokenService.java index aa346de..78d4eff 100644 --- a/chatto/src/main/java/org/ros/chatto/service/UserTokenService.java +++ b/chatto/src/main/java/org/ros/chatto/service/UserTokenService.java @@ -1,6 +1,5 @@ package org.ros.chatto.service; - import org.ros.chatto.model.UserToken; import org.ros.chatto.repository.TokenRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -18,32 +17,31 @@ import lombok.extern.slf4j.Slf4j; public class UserTokenService { @Autowired private TokenRepository tokenRepository; - + @Transactional(readOnly = true) - @Cacheable(value = "userTokenCache", key = "#userName", unless="#result == null") - public UserToken getTokenByUserName(String userName) - { - log.debug("Inside 1"); + @Cacheable(value = "userTokenCache", key = "#userName", unless = "#result == null") + public UserToken getTokenByUserName(String userName) { return tokenRepository.findByUserName(userName); } - + @Transactional(readOnly = true) - @Cacheable(value = "userTokenCache", key = "#tokenString", unless="#result == null") - public UserToken getTokenByTokenString(String tokenString) - { - log.debug("Inside 2"); + @Cacheable(value = "userTokenCache", key = "#tokenString", unless = "#result == null") + public UserToken getTokenByTokenString(String tokenString) { return tokenRepository.findByToken(tokenString); } - - public void saveToken(UserToken userToken) - { - log.info("Saving auth token"); + + public void saveToken(UserToken userToken) { + log.trace("Saving auth token"); tokenRepository.save(userToken); } - - public void deleteToken(String userName) - { - log.info("Deleting token for {}", userName); + + public void deleteToken(String userName) { + log.trace("Deleting token for {}", userName); tokenRepository.deleteByUserName(userName); } + + public void deleteToken(UserToken userToken) { + log.trace("Deleting token for {}", userToken); + tokenRepository.delete(userToken); + } }