-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from musabbozkurt/refactor
refactor
- Loading branch information
Showing
19 changed files
with
438 additions
and
31 deletions.
There are no files selected for viewing
25 changes: 25 additions & 0 deletions
25
src/main/java/com/mb/inventorymanagementservice/common/context/Context.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.mb.inventorymanagementservice.common.context; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.time.ZoneOffset; | ||
import java.util.Locale; | ||
|
||
@Data | ||
@Builder | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class Context { | ||
|
||
private String username; | ||
private String email; | ||
private String mobileNumber; | ||
private Locale language; | ||
private String ipAddress; | ||
private String userAgent; | ||
private boolean admin; | ||
private ZoneOffset preferredZoneOffset; | ||
} |
139 changes: 139 additions & 0 deletions
139
src/main/java/com/mb/inventorymanagementservice/common/context/ContextFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package com.mb.inventorymanagementservice.common.context; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.mb.inventorymanagementservice.exception.BaseException; | ||
import com.mb.inventorymanagementservice.exception.InvalidParameterException; | ||
import com.mb.inventorymanagementservice.exception.LocalizedException; | ||
import com.mb.inventorymanagementservice.utils.Constants; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.jboss.logging.MDC; | ||
import org.springframework.core.annotation.Order; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import java.io.IOException; | ||
import java.time.Instant; | ||
import java.time.ZoneOffset; | ||
import java.util.Collection; | ||
import java.util.Locale; | ||
import java.util.Objects; | ||
import java.util.TimeZone; | ||
|
||
@Slf4j | ||
@Order(1) | ||
@Component | ||
@RequiredArgsConstructor | ||
public class ContextFilter extends OncePerRequestFilter { | ||
|
||
public static final String ACCEPT_LANGUAGE_HEADER = "Accept-Language"; | ||
public static final String FORWARDED_FOR_HEADER = "x-forwarded-for"; | ||
public static final String USER_AGENT_HEADER = "user-agent"; | ||
public static final String ZONE_ID_HEADER = "X-ZONE-ID"; | ||
public static final String ADMIN_PERMISSION = "ADMIN"; | ||
|
||
private final ObjectMapper objectMapper; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
try { | ||
Context context = ContextHolder.get(); | ||
populateHeaderDetails(context, request); | ||
populateSecurityContext(context); | ||
ContextHolder.set(context); | ||
populateTracing(); | ||
filterChain.doFilter(request, response); | ||
} catch (BaseException e) { | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setStatus(e.getErrorCode().getHttpStatus().value()); | ||
response.getWriter().write(objectMapper.writeValueAsString(new LocalizedException(e.getErrorCode().getCode()))); | ||
response.getWriter().flush(); | ||
response.getWriter().close(); | ||
} finally { | ||
ContextHolder.clear(); | ||
MDC.clear(); | ||
} | ||
} | ||
|
||
private void populateHeaderDetails(Context context, HttpServletRequest httpServletRequest) { | ||
Locale language = getLanguageHeader(httpServletRequest); | ||
context.setLanguage(language); | ||
|
||
context.setIpAddress(getIpAddress(httpServletRequest)); | ||
context.setUserAgent(getHeader(httpServletRequest, USER_AGENT_HEADER)); | ||
|
||
String zoneId = getHeader(httpServletRequest, ZONE_ID_HEADER); | ||
context.setPreferredZoneOffset(zoneId == null ? ZoneOffset.UTC : TimeZone.getTimeZone(zoneId).toZoneId().getRules().getOffset(Instant.now())); | ||
} | ||
|
||
private String getHeader(HttpServletRequest httpServletRequest, String headerName) { | ||
return httpServletRequest.getHeader(headerName); | ||
} | ||
|
||
private Locale getLanguageHeader(HttpServletRequest request) { | ||
String value = request.getHeader(ACCEPT_LANGUAGE_HEADER); | ||
if (StringUtils.isNotBlank(value)) { | ||
try { | ||
return Locale.of(value); | ||
} catch (Exception ex) { | ||
throw new InvalidParameterException(); | ||
} | ||
} | ||
return Locale.of("EN"); | ||
} | ||
|
||
private String getIpAddress(HttpServletRequest request) { | ||
final String xForwardedForHeader = request.getHeader(FORWARDED_FOR_HEADER); | ||
if (xForwardedForHeader == null) { | ||
return request.getRemoteAddr(); | ||
} | ||
final String[] tokenized = xForwardedForHeader.trim().split(","); | ||
return tokenized.length == 0 ? null : tokenized[0].trim(); | ||
} | ||
|
||
private void populateSecurityContext(Context context) { | ||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); | ||
if (authentication instanceof UsernamePasswordAuthenticationToken authenticationToken) { | ||
if (authenticationToken.getDetails() != null) { | ||
UserDetails userDetails = (UserDetails) authentication.getPrincipal(); | ||
context.setAdmin(isAdmin(authentication.getAuthorities())); | ||
context.setUsername(userDetails.getUsername()); | ||
} | ||
} | ||
} | ||
|
||
private boolean isAdmin(Collection<? extends GrantedAuthority> authorities) { | ||
return authorities.stream().map(GrantedAuthority::getAuthority).anyMatch(ADMIN_PERMISSION::equals); | ||
} | ||
|
||
private void populateTracing() { | ||
if (Objects.nonNull(ContextHolder.get().getUsername())) { | ||
MDC.put("userName", ContextHolder.get().getUsername()); | ||
} | ||
|
||
if (Objects.nonNull(ContextHolder.get().getIpAddress())) { | ||
MDC.put("ipAddress", ContextHolder.get().getIpAddress()); | ||
} | ||
} | ||
|
||
@Override | ||
public void destroy() { | ||
log.info("ContextFilter is destroyed."); | ||
} | ||
|
||
@Override | ||
protected boolean shouldNotFilter(HttpServletRequest request) { | ||
return Constants.isUriExcluded(request.getRequestURI()); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
src/main/java/com/mb/inventorymanagementservice/common/context/ContextHolder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.mb.inventorymanagementservice.common.context; | ||
|
||
import lombok.AccessLevel; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.util.Objects; | ||
|
||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
public class ContextHolder { | ||
|
||
private static final ThreadLocal<Context> THREAD_LOCAL = new InheritableThreadLocal<>(); | ||
|
||
public static Context get() { | ||
return Objects.isNull(THREAD_LOCAL.get()) ? Context.builder().build() : THREAD_LOCAL.get(); | ||
} | ||
|
||
public static void set(Context context) { | ||
THREAD_LOCAL.set(context); | ||
} | ||
|
||
public static void clear() { | ||
THREAD_LOCAL.remove(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.