test master 변경

This commit is contained in:
현성필
2024-01-23 11:24:09 +09:00
parent 75a695897d
commit 91e902af25
121 changed files with 329 additions and 338 deletions
@@ -0,0 +1,18 @@
package com.eactive.testmaster;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication(exclude = {
UserDetailsServiceAutoConfiguration.class,
})
@ComponentScan("com.eactive")
public class ElinkTestMaster {
public static void main(String[] args) {
SpringApplication.run(ElinkTestMaster.class, args);
}
}
@@ -0,0 +1,14 @@
package com.eactive.testmaster.api.condition;
import com.eactive.testmaster.api.dto.MockRequest;
import com.eactive.testmaster.api.entity.MockCondition;
import java.util.Map;
public interface ConditionMatcher {
boolean matches(MockCondition condition, MockRequest request, Map<String, String> pathVariables);
String getType();
String getDisplayName();
}
@@ -0,0 +1,29 @@
package com.eactive.testmaster.api.condition;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service(value = "ConditionMatcherService")
public class ConditionMatcherService {
private final Map<String, ConditionMatcher> matchers;
private final List<ConditionMatcher> matchersList;
public ConditionMatcherService(List<ConditionMatcher> matchersList) {
this.matchersList = matchersList;
this.matchers = this.matchersList.stream().collect(Collectors.toMap(ConditionMatcher::getType, Function.identity()));
}
public ConditionMatcher getMatcher(String key) {
return matchers.get(key);
}
public List<ConditionMatcher> getMatchersList() {
return matchersList;
}
}
@@ -0,0 +1,25 @@
package com.eactive.testmaster.api.condition;
import com.eactive.testmaster.api.dto.MockRequest;
import com.eactive.testmaster.api.entity.MockCondition;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class HeaderMatcher implements ConditionMatcher {
@Override
public boolean matches(MockCondition condition, MockRequest request, Map<String, String> pathVariables) {
return condition.getValue().equals(request.getHeader(condition.getKey()));
}
@Override
public String getType() {
return "header";
}
@Override
public String getDisplayName() {
return "Header";
}
}
@@ -0,0 +1,33 @@
package com.eactive.testmaster.api.condition;
import com.eactive.testmaster.api.dto.MockRequest;
import com.eactive.testmaster.api.entity.MockCondition;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class JsonPathMatcher implements ConditionMatcher {
@Override
public boolean matches(MockCondition condition, MockRequest request, Map<String, String> pathVariables) {
try {
Object value = JsonPath.read(request.getBody(), condition.getKey());
return value.toString().equals(condition.getValue());
} catch (PathNotFoundException e) {
return false;
}
}
@Override
public String getType() {
return "jsonpath";
}
@Override
public String getDisplayName() {
return "JSON Path";
}
}
@@ -0,0 +1,26 @@
package com.eactive.testmaster.api.condition;
import com.eactive.testmaster.api.dto.MockRequest;
import com.eactive.testmaster.api.entity.MockCondition;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class PathMatcher implements ConditionMatcher {
@Override
public boolean matches(MockCondition condition, MockRequest request, Map<String, String> pathVariables) {
return condition.getValue().equals(pathVariables.get(condition.getKey()));
}
@Override
public String getType() {
return "path";
}
@Override
public String getDisplayName() {
return "Path";
}
}
@@ -0,0 +1,26 @@
package com.eactive.testmaster.api.condition;
import com.eactive.testmaster.api.dto.MockRequest;
import com.eactive.testmaster.api.entity.MockCondition;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class QueryMatcher implements ConditionMatcher {
@Override
public boolean matches(MockCondition condition, MockRequest request, Map<String, String> pathVariables) {
return condition.getValue().equals(request.getParam(condition.getKey()));
}
@Override
public String getType() {
return "query";
}
@Override
public String getDisplayName() {
return "Query";
}
}
@@ -0,0 +1,123 @@
package com.eactive.testmaster.api.controller;
import com.eactive.testmaster.api.condition.ConditionMatcher;
import com.eactive.testmaster.api.condition.ConditionMatcherService;
import com.eactive.testmaster.api.dto.MockRequest;
import com.eactive.testmaster.api.entity.MockCondition;
import com.eactive.testmaster.api.entity.MockResponse;
import com.eactive.testmaster.api.entity.MockRoute;
import com.eactive.testmaster.api.service.MockRouteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
public class MockApiController {
private final MockRouteService mockRouteService;
private final ConditionMatcherService conditionMatcherService;
public MockApiController(MockRouteService mockRouteService, ConditionMatcherService conditionMatcherService) {
this.mockRouteService = mockRouteService;
this.conditionMatcherService = conditionMatcherService;
}
private static final Logger logger = LoggerFactory.getLogger(MockApiController.class);
public ResponseEntity<String> handleRequest(HttpServletRequest httpRequest) throws IOException {
MockRequest request = new MockRequest(httpRequest);
AntPathMatcher pathMatcher = new AntPathMatcher();
logger.debug("===========================");
logger.debug("Request: " + request.getMethod() + " " + request.getUri());
logger.debug("Headers:" + request.getHeaders().toString());
logger.debug("Params: " + request.getParams().toString());
logger.debug("Body: ");
logger.debug(request.getBody());
logger.debug("===========================");
for (MockRoute mockRoute : mockRouteService.getAllRoutes()) {
if (isRouteMatch(mockRoute, request)) {
Map<String, String> pathVariables = pathMatcher.extractUriTemplateVariables(mockRoute.getPathPattern(), request.getUri());
MockResponse matchingResponse = findMatchingResponse(mockRoute, request, pathVariables);
if (matchingResponse != null) {
if (matchingResponse.getDelay() > 0) {
try {
Thread.sleep(matchingResponse.getDelay());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("Error while delaying response", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error processing request due to interruption.");
}
}
HttpHeaders responseHeaders = new HttpHeaders();
matchingResponse.getHeaders().forEach(responseHeaders::set);
request.getParams().putAll(pathVariables);
return new ResponseEntity<>(replaceParams(matchingResponse.getBody(), request.getParams()), responseHeaders, HttpStatus.valueOf(matchingResponse.getStatusCode()));
}
}
}
// If no matching route or response is found, return a 404 Not Found
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Not Found");
}
private String replaceParams(String responseBody, Map<String, String> params) {
for (Map.Entry<String, String> entry : params.entrySet()) {
responseBody = responseBody.replace("{" + entry.getKey() + "}", entry.getValue());
}
return responseBody;
}
private boolean isRouteMatch(MockRoute mockRoute, MockRequest request) {
AntPathMatcher pathMatcher = new AntPathMatcher();
return mockRoute.getMethod().equalsIgnoreCase(request.getMethod()) && pathMatcher.match(mockRoute.getPathPattern(), request.getUri());
}
private MockResponse findMatchingResponse(MockRoute mockRoute, MockRequest mockRequest, Map<String, String> pathVariables) {
MockResponse defaultResponse = mockRoute.getResponses().stream().filter(MockResponse::isDefaultResponse).findFirst().orElse(null);
List<MockResponse> responses = mockRoute.getResponses().stream().filter(response -> !response.isDefaultResponse()).collect(Collectors.toList());
if (defaultResponse != null) {
responses.add(defaultResponse);
}
for (MockResponse response : responses) {
if (response.getConditions() == null || isRequestMatching(response.getConditions(), mockRequest, pathVariables)) {
return response;
}
}
return null;
}
private boolean isRequestMatching(List<MockCondition> conditions, MockRequest request, Map<String, String> pathVariables) {
if (conditions == null) {
return true;
}
for (MockCondition condition : conditions) {
ConditionMatcher matcher = conditionMatcherService.getMatcher(condition.getType().toLowerCase());
if (matcher != null && !matcher.matches(condition, request, pathVariables)) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,61 @@
package com.eactive.testmaster.api.controller;
import com.eactive.testmaster.api.entity.MockRoute;
import com.eactive.testmaster.api.service.MockRouteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
@Component
public class MockApiHandlerMapping {
private static final Logger logger = LoggerFactory.getLogger(MockApiHandlerMapping.class);
@Autowired
private MockRouteService mockRouteService;
@Autowired
private MockApiController mockApiController;
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@PostConstruct
public void initialize() {
mockRouteService.initAllRoutes();
for (MockRoute mockRoute : mockRouteService.getAllRoutes()) {
registerMapping(mockRoute);
}
}
public void registerMapping(MockRoute mockRoute) {
RequestMappingInfo mappingInfo = RequestMappingInfo
.paths(mockRoute.getPathPattern())
.methods(RequestMethod.valueOf(mockRoute.getMethod()))
.build();
try {
requestMappingHandlerMapping.registerMapping(mappingInfo, mockApiController, MockApiController.class.getDeclaredMethod("handleRequest", HttpServletRequest.class));
logger.info("Registered mapping for {} {}", mockRoute.getMethod(), mockRoute.getPathPattern());
} catch (NoSuchMethodException e) {
logger.error("Error registering mapping", e);
}
}
public void unregisterMapping(MockRoute mockRoute) {
RequestMappingInfo mappingInfo = RequestMappingInfo
.paths(mockRoute.getPathPattern())
.methods(RequestMethod.valueOf(mockRoute.getMethod()))
.build();
requestMappingHandlerMapping.unregisterMapping(mappingInfo);
logger.info("Unregistered mapping for {} {}", mockRoute.getMethod(), mockRoute.getPathPattern());
}
}
@@ -0,0 +1,156 @@
package com.eactive.testmaster.api.controller;
import com.eactive.testmaster.api.dto.MockResponseDTO;
import com.eactive.testmaster.api.dto.MockRouteRegisterDTO;
import com.eactive.testmaster.api.dto.MockRouteSearch;
import com.eactive.testmaster.api.dto.MockRouteUpdateDTO;
import com.eactive.testmaster.api.entity.MockRoute;
import com.eactive.testmaster.api.mapper.MockRouteMapper;
import com.eactive.testmaster.api.service.MockRouteService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.PostConstruct;
import java.util.List;
@Controller
@RequestMapping("/mgmt/routes")
public class MockRouteMgmtController {
private static final String MOCK_ROUTE = "mockRoute";
@PostConstruct
public void init() {
mockRouteService.initAllRoutes();
}
private final MockRouteService mockRouteService;
private final Validator validator;
private final MockRouteMapper mockRouteMapper;
private final MockApiHandlerMapping mockApiHandlerMapping;
public MockRouteMgmtController(MockRouteService mockRouteService, Validator validator, MockRouteMapper mockRouteMapper, MockApiHandlerMapping mockApiHandlerMapping) {
this.mockRouteService = mockRouteService;
this.validator = validator;
this.mockRouteMapper = mockRouteMapper;
this.mockApiHandlerMapping = mockApiHandlerMapping;
}
public static final String MOCK_ROUTE_EDIT = "page/routes/mockRouteEdit";
public static final String MOCK_ROUTE_LIST_VIEW = "redirect:/mgmt/routes/list_view.do";
@GetMapping("/list_view.do")
public ModelAndView listView(@ModelAttribute("mockRouteSearch") MockRouteSearch mockRouteSearch, Pageable pageable) {
ModelAndView mav = new ModelAndView("page/routes/mockRouteList");
Page<MockRoute> page = mockRouteService.findAll(mockRouteSearch.buildSpecification(), pageable);
mav.addObject("page", page);
mav.addObject("pageable", pageable);
mav.addObject("modelSearch", mockRouteSearch);
return mav;
}
@GetMapping("/create_view.do")
public ModelAndView createView() {
ModelAndView mav = new ModelAndView(MOCK_ROUTE_EDIT);
MockRouteRegisterDTO defaultRoute = new MockRouteRegisterDTO();
MockResponseDTO defaultResponse = new MockResponseDTO();
defaultResponse.setDefaultResponse("true");
defaultRoute.getResponses().add(defaultResponse);
mav.addObject(MOCK_ROUTE, defaultRoute);
return mav;
}
@GetMapping("/update_view.do")
public ModelAndView updateView(@RequestParam("id") Long id) {
ModelAndView mav = new ModelAndView(MOCK_ROUTE_EDIT);
MockRoute found = mockRouteService.findById(id);
mav.addObject(MOCK_ROUTE, mockRouteMapper.map(found));
return mav;
}
@PostMapping("/clone_view.do")
public ModelAndView cloneView(@RequestParam("id") Long id) {
ModelAndView mav = new ModelAndView(MOCK_ROUTE_EDIT);
MockRoute found = mockRouteService.findById(id);
MockRouteRegisterDTO dto = mockRouteMapper.map(found);
dto.setId(null);
mav.addObject(MOCK_ROUTE, dto);
return mav;
}
@PostMapping("/register.do")
public String register(@ModelAttribute("mockRoute") MockRouteRegisterDTO dto, BindingResult bindingResult, ModelMap model) {
if (dto == null) {
return "redirect:/mgmt/routes/create_view.do";
}
validator.validate(dto, bindingResult);
if (bindingResult.hasErrors()) {
model.addAttribute(MOCK_ROUTE, dto);
return MOCK_ROUTE_EDIT;
}
MockRoute mockRoute = mockRouteMapper.map(dto);
mockRouteService.create(mockRoute);
mockRouteService.initAllRoutes();
mockApiHandlerMapping.registerMapping(mockRoute);
return MOCK_ROUTE_LIST_VIEW;
}
@PostMapping("/update.do")
public String update(@RequestParam("id") String id,
@ModelAttribute("mockRoute") MockRouteUpdateDTO dto,
BindingResult bindingResult, ModelMap model) {
if (StringUtils.isEmpty(id)) {
return MOCK_ROUTE_LIST_VIEW;
}
validator.validate(dto, bindingResult);
if (bindingResult.hasErrors()) {
model.addAttribute(MOCK_ROUTE, dto);
return MOCK_ROUTE_EDIT;
}
MockRoute prev = mockRouteService.findById(Long.parseLong(id));
mockApiHandlerMapping.unregisterMapping(prev);
MockRoute mockRoute = mockRouteMapper.map(dto);
mockRouteService.update(Long.parseLong(id), mockRoute);
mockRouteService.initAllRoutes();
mockApiHandlerMapping.registerMapping(mockRoute);
return MOCK_ROUTE_LIST_VIEW;
}
@PostMapping("/delete.do")
public String delete(@RequestParam("checkedIdForDel") List<String> ids) {
for (String id : ids) {
MockRoute mockRoute = mockRouteService.findById(Long.parseLong(id));
mockApiHandlerMapping.unregisterMapping(mockRoute);
mockRouteService.deleteById(Long.parseLong(id));
}
mockRouteService.initAllRoutes();
return MOCK_ROUTE_LIST_VIEW;
}
}
@@ -0,0 +1,33 @@
package com.eactive.testmaster.api.dto;
public class MockConditionDTO {
private String type;
private String key;
private String value;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@@ -0,0 +1,23 @@
package com.eactive.testmaster.api.dto;
public class MockHeaderDTO {
private String key;
private String value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@@ -0,0 +1,96 @@
package com.eactive.testmaster.api.dto;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class MockRequest {
public static Map<String, String> parseQueryString(HttpServletRequest request) {
Map<String, String> queryParams = new HashMap<>();
String queryString = request.getQueryString();
if (queryString != null) {
String[] queryParamsArr = queryString.split("&");
for (String queryParam : queryParamsArr) {
String[] pair = queryParam.split("=");
String key = pair[0];
String value = "";
if (pair.length > 1) {
value = pair[1];
}
queryParams.put(key, value);
}
}
return queryParams;
}
public MockRequest(HttpServletRequest request) throws IOException {
this.method = request.getMethod();
this.uri = request.getRequestURI();
this.headers = Collections.list(request.getHeaderNames()).stream()
.collect(Collectors.toMap(name -> name, request::getHeader));
this.params = parseQueryString(request);
this.body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
}
private String uri;
private String method;
private String body;
private Map<String, String> headers;
private Map<String, String> params;
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public String getHeader(String key) {
return this.headers.get(key);
}
public String getParam(String key) {
return this.params.get(key);
}
public Map<String, String> getParams() {
return params;
}
public void setParams(Map<String, String> params) {
this.params = params;
}
}
@@ -0,0 +1,68 @@
package com.eactive.testmaster.api.dto;
import javax.validation.constraints.Min;
import java.util.ArrayList;
import java.util.List;
public class MockResponseDTO {
@Min(100)
private int statusCode = 200;
private List<MockHeaderDTO> headers = new ArrayList<>();
private String body;
private long delay = 0; //millisecond
private String defaultResponse;
private List<MockConditionDTO> conditions;
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public List<MockHeaderDTO> getHeaders() {
return headers;
}
public void setHeaders(List<MockHeaderDTO> headers) {
this.headers = headers;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public long getDelay() {
return delay;
}
public void setDelay(long delay) {
this.delay = delay;
}
public List<MockConditionDTO> getConditions() {
return conditions;
}
public void setConditions(List<MockConditionDTO> conditions) {
this.conditions = conditions;
}
public String getDefaultResponse() {
return defaultResponse;
}
public void setDefaultResponse(String defaultResponse) {
this.defaultResponse = defaultResponse;
}
}
@@ -0,0 +1,76 @@
package com.eactive.testmaster.api.dto;
import com.eactive.testmaster.common.validator.UniqueRoute;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;
@UniqueRoute(path = "pathPattern", method = "method")
public class MockRouteRegisterDTO {
private Long id;
@NotEmpty
private String method;
@NotEmpty
private String pathPattern;
@NotEmpty
private String name;
private String description;
@Size(min = 1, message = "Response must have at least one item")
private List<MockResponseDTO> responses = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getPathPattern() {
return pathPattern;
}
public void setPathPattern(String pathPattern) {
this.pathPattern = pathPattern;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<MockResponseDTO> getResponses() {
return responses;
}
public void setResponses(List<MockResponseDTO> responses) {
this.responses = responses;
}
}
@@ -0,0 +1,31 @@
package com.eactive.testmaster.api.dto;
import com.eactive.testmaster.api.entity.MockRoute;
import com.eactive.testmaster.common.search.BaseSearch;
import com.eactive.testmaster.common.search.ColumnSearchModel;
import com.eactive.testmaster.common.search.SearchCondition;
import com.eactive.testmaster.common.search.SearchModel;
import java.util.ArrayList;
import java.util.List;
public class MockRouteSearch implements BaseSearch<MockRoute> {
private String name;
@Override
public List<SearchModel> buildSearchCondition() {
List<SearchModel> models = new ArrayList<>();
models.add(new ColumnSearchModel("name", SearchCondition.LIKE, name));
return models;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,73 @@
package com.eactive.testmaster.api.dto;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;
public class MockRouteUpdateDTO {
private Long id;
@NotEmpty
private String method;
@NotEmpty
private String pathPattern;
@NotEmpty
private String name;
private String description;
@Size(min = 1, message = "Response must have at least one item")
private List<MockResponseDTO> responses = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getPathPattern() {
return pathPattern;
}
public void setPathPattern(String pathPattern) {
this.pathPattern = pathPattern;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<MockResponseDTO> getResponses() {
return responses;
}
public void setResponses(List<MockResponseDTO> responses) {
this.responses = responses;
}
}
@@ -0,0 +1,63 @@
package com.eactive.testmaster.api.entity;
import javax.persistence.*;
@Entity
@Table(name = "MOCK_RESPONSE_CONDITION")
public class MockCondition {
@ManyToOne
@JoinColumn(name = "MOCK_RESPONSE_ID", nullable = false)
private MockResponse mockResponse;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String type;
private String key;
private String value;
public MockResponse getMockResponse() {
return mockResponse;
}
public void setMockResponse(MockResponse mockResponse) {
this.mockResponse = mockResponse;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@@ -0,0 +1,103 @@
package com.eactive.testmaster.api.entity;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Entity
@Table(name = "MOCK_RESPONSE")
public class MockResponse {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private int statusCode = 200;
@ElementCollection
@CollectionTable(name = "MOCK_RESPONSE_HEADERS", joinColumns = @JoinColumn(name = "RESPONSE_ID"))
@MapKeyColumn(name = "HEADER_KEY")
@Column(name = "HEADER_VALUE")
private Map<String, String> headers = new HashMap<>();
@Lob
private String body;
private long delay = 0; //millisecond
@Column(name = "DEFAULT_RESPONSE", nullable = false, columnDefinition = "boolean default false")
private boolean defaultResponse = false;
@ManyToOne
@JoinColumn(name = "ROUTE_ID", nullable = false)
private MockRoute route;
@OneToMany(mappedBy = "mockResponse", cascade = CascadeType.ALL, orphanRemoval = true)
private List<MockCondition> conditions = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public long getDelay() {
return delay;
}
public void setDelay(long delay) {
this.delay = delay;
}
public MockRoute getRoute() {
return route;
}
public void setRoute(MockRoute route) {
this.route = route;
}
public List<MockCondition> getConditions() {
return conditions;
}
public void setConditions(List<MockCondition> conditions) {
this.conditions = conditions;
}
public boolean isDefaultResponse() {
return defaultResponse;
}
public void setDefaultResponse(boolean defaultResponse) {
this.defaultResponse = defaultResponse;
}
}
@@ -0,0 +1,76 @@
package com.eactive.testmaster.api.entity;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "MOCK_ROUTE")
public class MockRoute {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String method;
private String pathPattern;
private String name;
private String description;
@OneToMany(mappedBy = "route", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private List<MockResponse> responses = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getPathPattern() {
return pathPattern;
}
public void setPathPattern(String pathPattern) {
this.pathPattern = pathPattern;
}
public List<MockResponse> getResponses() {
return responses;
}
public void setResponses(List<MockResponse> responses) {
this.responses = responses;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
@@ -0,0 +1,105 @@
package com.eactive.testmaster.api.mapper;
import com.eactive.testmaster.api.dto.MockHeaderDTO;
import com.eactive.testmaster.api.dto.MockResponseDTO;
import com.eactive.testmaster.api.dto.MockRouteRegisterDTO;
import com.eactive.testmaster.api.dto.MockRouteUpdateDTO;
import com.eactive.testmaster.api.entity.MockResponse;
import com.eactive.testmaster.api.entity.MockRoute;
import com.eactive.testmaster.common.mapper.CommonMapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public abstract class MockRouteMapper {
private static final Map<String, String> XML_ESCAPE_MAP;
static {
XML_ESCAPE_MAP = new HashMap<>();
XML_ESCAPE_MAP.put("&lt;", "<");
XML_ESCAPE_MAP.put("&gt;", ">");
XML_ESCAPE_MAP.put("&amp;", "&");
XML_ESCAPE_MAP.put("&quot;", "\"");
XML_ESCAPE_MAP.put("&apos;", "'");
}
public abstract MockRoute map(MockRouteRegisterDTO dto);
public abstract MockRoute map(MockRouteUpdateDTO dto);
public abstract MockRouteRegisterDTO map(MockRoute entity);
@Mapping(target = "headers", source = "headers", qualifiedByName = "mapHeadersToMap")
@Mapping(target = "defaultResponse", source = "defaultResponse", qualifiedByName = "mapEntityDefaultResponse")
@Mapping(target = "body", source="body" , qualifiedByName = "unescapeBody")
public abstract MockResponse map(MockResponseDTO dto);
@Mapping(target = "headers", source = "headers", qualifiedByName = "mapHeadersToList")
@Mapping(target = "defaultResponse", source = "defaultResponse", qualifiedByName = "mapDTODefaultResponse")
@Mapping(target = "body", source="body" , qualifiedByName = "unescapeBody")
public abstract MockResponseDTO map(MockResponse entity);
@Named("unescapeBody")
public static String unescapeXml(String escapedXml) {
if (escapedXml == null || escapedXml.isEmpty()) {
return escapedXml;
}
for (Map.Entry<String, String> entry : XML_ESCAPE_MAP.entrySet()) {
escapedXml = escapedXml.replace(entry.getKey(), entry.getValue());
}
return escapedXml;
}
@Named("mapDTODefaultResponse")
public String map(boolean value) {
return value ? "true" : "false";
}
@Named("mapEntityDefaultResponse")
public boolean map(String value) {
if (value == null) return false;
return value.equals("true");
}
@Named("mapHeadersToList")
public List<MockHeaderDTO> mapHeadersToList(Map<String, String> headers) {
List<MockHeaderDTO> list = new ArrayList<>();
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
MockHeaderDTO header = new MockHeaderDTO();
header.setKey(key);
header.setValue(value);
list.add(header);
}
}
return list;
}
@Named("mapHeadersToMap")
public Map<String, String> mapHeadersToMap(List<MockHeaderDTO> headers) {
Map<String, String> map = new HashMap<>();
if (headers != null) {
for (MockHeaderDTO header : headers) {
map.put(header.getKey(), header.getValue());
}
}
return map;
}
}
@@ -0,0 +1,10 @@
package com.eactive.testmaster.api.repository;
import com.eactive.testmaster.api.entity.MockRoute;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface MockRouteRepository extends JpaRepository<MockRoute, Long>, JpaSpecificationExecutor<MockRoute> {
long countByMethodAndPathPattern(String method, String pathPattern);
}
@@ -0,0 +1,121 @@
package com.eactive.testmaster.api.service;
import com.eactive.testmaster.api.entity.MockCondition;
import com.eactive.testmaster.api.entity.MockResponse;
import com.eactive.testmaster.api.entity.MockRoute;
import com.eactive.testmaster.api.repository.MockRouteRepository;
import com.eactive.testmaster.common.exception.NotFoundException;
import org.apache.commons.beanutils.BeanUtils;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
@Service
@Transactional
public class MockRouteService {
private static final String NOT_FOUND_MESSAGE = "Route Not Found";
@Autowired
MockRouteRepository mockRouteRepository;
private List<MockRoute> allRoutes = new ArrayList<>();
public List<MockRoute> getAllRoutes() {
return allRoutes;
}
public void setAllRoutes(List<MockRoute> allRoutes) {
this.allRoutes = allRoutes;
}
public Page<MockRoute> findAll(Specification<MockRoute> mockRouteSpecification, Pageable pageable) {
return mockRouteRepository.findAll(mockRouteSpecification, pageable);
}
public MockRoute findById(Long id) {
return mockRouteRepository.findById(id).orElseThrow(() -> new NotFoundException(NOT_FOUND_MESSAGE));
}
public void create(MockRoute mockRoute) {
if (!mockRoute.getPathPattern().startsWith("/")) {
mockRoute.setPathPattern("/" + mockRoute.getPathPattern());
}
for (MockResponse response : mockRoute.getResponses()) {
response.setRoute(mockRoute);
if (response.getConditions() != null) {
for (MockCondition condition : response.getConditions()) {
condition.setMockResponse(response);
}
}
}
mockRouteRepository.save(mockRoute);
}
public void deleteById(long id) {
mockRouteRepository.deleteById(id);
}
public void update(Long id, MockRoute updated) {
MockRoute route = mockRouteRepository.findById(id).orElseThrow(() -> new NotFoundException(NOT_FOUND_MESSAGE));
route.getResponses().clear();
route.setName(updated.getName());
route.setDescription(updated.getDescription());
// route.setMethod(updated.getMethod());
// route.setPathPattern(updated.getPathPattern());
if (!route.getPathPattern().startsWith("/")) {
route.setPathPattern("/" + route.getPathPattern());
}
route.getResponses().addAll(updated.getResponses());
for (MockResponse response : route.getResponses()) {
response.setRoute(route);
if (response.getConditions() != null) {
for (MockCondition condition : response.getConditions()) {
condition.setMockResponse(response);
}
}
}
mockRouteRepository.save(route);
}
public void initAllRoutes() {
allRoutes.clear();
List<MockRoute> routes = mockRouteRepository.findAll();
for (MockRoute route : routes) {
for (MockResponse response : route.getResponses()) {
if (response.getConditions() != null) {
for (MockCondition condition : response.getConditions()) {
Hibernate.initialize(condition.getMockResponse());
}
}
if (response.getHeaders() != null) {
Hibernate.initialize(response.getHeaders());
}
Hibernate.initialize(response);
}
try {
MockRoute cloned = (MockRoute) BeanUtils.cloneBean(route);
allRoutes.add(cloned);
} catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
}
@@ -0,0 +1,36 @@
package com.eactive.testmaster.client.controller;
import com.eactive.testmaster.client.dto.ApiRequestDTO;
import com.eactive.testmaster.client.dto.ApiResponse;
import com.eactive.testmaster.client.service.NettyApiClient;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ExecutionException;
@RestController
public class ApiClientController {
private final NettyApiClient nettyApiClient;
private final Validator validator;
public ApiClientController(NettyApiClient nettyApiClient, Validator validator) {
this.nettyApiClient = nettyApiClient;
this.validator = validator;
}
@PostMapping("/mgmt/api/test.do")
public ApiResponse handleApiRequest(@RequestBody ApiRequestDTO request, BindingResult bindingResult) throws InterruptedException, ExecutionException {
validator.validate(request, bindingResult);
if (bindingResult.hasErrors()) {
throw new IllegalArgumentException(bindingResult.getAllErrors().get(0).getDefaultMessage());
}
return nettyApiClient.handleRequest(request).get();
}
}
@@ -0,0 +1,29 @@
package com.eactive.testmaster.client.controller;
import com.eactive.testmaster.server.entity.Server;
import com.eactive.testmaster.server.entity.ServerRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
public class ApiClientPageController {
private final ServerRepository serverRepository;
public ApiClientPageController(ServerRepository serverRepository) {
this.serverRepository = serverRepository;
}
@GetMapping("/mgmt/api_tester.do")
public String testView(ModelMap model) {
List<Server> servers = serverRepository.findAllByEnabledIs(true);
model.addAttribute("servers", servers);
return "page/tester/apiTester";
}
}
@@ -0,0 +1,83 @@
package com.eactive.testmaster.client.controller;
import com.eactive.testmaster.client.dto.ApiCollectionDTO;
import com.eactive.testmaster.client.dto.ApiRequestDTO;
import com.eactive.testmaster.client.entity.ApiCollection;
import com.eactive.testmaster.client.entity.ApiRequestInfo;
import com.eactive.testmaster.client.mapper.ApiRequestMapper;
import com.eactive.testmaster.client.service.ApiRequestMgmtService;
import com.eactive.testmaster.common.util.SecurityUtil;
import com.eactive.testmaster.user.entity.StaffUser;
import com.eactive.testmaster.user.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
public class ApiRequestMgmtController {
private static final String SUCCESS = "success";
private final ApiRequestMgmtService apiRequestMgmtService;
private final ApiRequestMapper apiRequestMapper;
private final UserService userService;
public ApiRequestMgmtController(ApiRequestMgmtService apiRequestMgmtService, ApiRequestMapper apiRequestMapper, UserService userService) {
this.apiRequestMgmtService = apiRequestMgmtService;
this.apiRequestMapper = apiRequestMapper;
this.userService = userService;
}
@GetMapping("/mgmt/collections/list.do")
public ResponseEntity<List<ApiCollectionDTO>> listApiRequests() {
StaffUser user = userService.findByEsntlId(SecurityUtil.getCurrentUserEsntlId());
List<ApiCollection> collections = apiRequestMgmtService.getMyApis(user);
return ResponseEntity.ok(apiRequestMapper.mapCollections(collections));
}
@PostMapping("/mgmt/collections/create.do")
public ResponseEntity<String> createApiCollection(
@RequestBody ApiCollectionDTO dto) {
ApiCollection collection = apiRequestMapper.map(dto);
StaffUser user = userService.findByEsntlId(SecurityUtil.getCurrentUserEsntlId());
collection.setOwner(user);
apiRequestMgmtService.createNewApiCollection(collection);
return ResponseEntity.ok(SUCCESS);
}
@PostMapping("/mgmt/collections/update.do")
public ResponseEntity<String> updateApiCollection(@RequestBody ApiCollectionDTO dto) {
ApiCollection updated = apiRequestMapper.map(dto);
apiRequestMgmtService.updateApiCollectionName(updated.getId(), updated);
return ResponseEntity.ok(SUCCESS);
}
@PostMapping("/mgmt/collections/delete.do")
public ResponseEntity<String> deleteApiCollection(@RequestBody ApiCollectionDTO dto) {
apiRequestMgmtService.deleteApiCollection(dto.getId());
return ResponseEntity.ok(SUCCESS);
}
@PostMapping("/mgmt/collections/{id}/apis/save.do")
public ResponseEntity<String> addApiToCollection(@PathVariable(name = "id") String id,
@RequestBody ApiRequestDTO dto) {
ApiRequestInfo apiRequestInfo = apiRequestMapper.map(dto);
StaffUser user = userService.findByEsntlId(SecurityUtil.getCurrentUserEsntlId());
apiRequestInfo.setOwner(user);
String apiId = apiRequestMgmtService.saveApiToCollection(id, apiRequestInfo);
return ResponseEntity.ok(apiId);
}
@PostMapping("/mgmt/collections/{id}/apis/delete.do")
public ResponseEntity<String> removeApiFromCollection(@PathVariable(name = "id") String id,
@RequestBody ApiRequestDTO dto) {
apiRequestMgmtService.removeApiFromCollection(id, dto.getId());
return ResponseEntity.ok(SUCCESS);
}
}
@@ -0,0 +1,61 @@
package com.eactive.testmaster.client.controller;
import com.eactive.testmaster.client.dto.ApiScenarioDTO;
import com.eactive.testmaster.client.entity.ApiScenario;
import com.eactive.testmaster.client.mapper.ApiScenarioMapper;
import com.eactive.testmaster.client.service.ApiScenarioMgmtService;
import com.eactive.testmaster.common.util.SecurityUtil;
import com.eactive.testmaster.user.entity.StaffUser;
import com.eactive.testmaster.user.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/mgmt/api_scenario")
public class ApiScenarioMgmtController {
private final ApiScenarioMgmtService apiScenarioMgmtService;
private final ApiScenarioMapper apiScenarioMapper;
private final UserService userService;
public ApiScenarioMgmtController(ApiScenarioMgmtService apiScenarioMgmtService, ApiScenarioMapper apiScenarioMapper, UserService userService) {
this.apiScenarioMgmtService = apiScenarioMgmtService;
this.apiScenarioMapper = apiScenarioMapper;
this.userService = userService;
}
@GetMapping("/list.do")
public ResponseEntity<List<ApiScenarioDTO>> listScenarios() {
List<ApiScenario> scenarios = apiScenarioMgmtService.findAll();
return ResponseEntity.ok(apiScenarioMapper.toDtoList(scenarios));
}
@PostMapping("/create.do")
public ResponseEntity<ApiScenarioDTO> createScenario(@RequestBody ApiScenarioDTO apiScenarioDto) {
ApiScenario apiScenario = apiScenarioMapper.map(apiScenarioDto);
StaffUser user = userService.findByEsntlId(SecurityUtil.getCurrentUserEsntlId());
apiScenario.setOwner(user);
ApiScenario createdScenario = apiScenarioMgmtService.createApiScenario(apiScenario);
return new ResponseEntity<>(apiScenarioMapper.map(createdScenario), HttpStatus.CREATED);
}
@PostMapping("/update.do")
public ResponseEntity<ApiScenarioDTO> updateScenario(@RequestBody ApiScenarioDTO apiScenarioDto) {
ApiScenario updatedApiScenario = apiScenarioMapper.map(apiScenarioDto);
ApiScenario updatedScenario = apiScenarioMgmtService.updateApiScenario(apiScenarioDto.getId(), updatedApiScenario);
return ResponseEntity.ok(apiScenarioMapper.map(updatedScenario));
}
@PostMapping("/delete.do")
public ResponseEntity<Void> deleteScenario(@RequestBody ApiScenarioDTO apiScenarioDto) {
apiScenarioMgmtService.deleteApiScenario(apiScenarioDto.getId());
return ResponseEntity.ok().build();
}
}
@@ -0,0 +1,39 @@
package com.eactive.testmaster.client.dto;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
public class ApiCollectionDTO {
private String id;
@NotEmpty
private String name;
private List<ApiRequestDTO> apis = new ArrayList<>();
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setApis(List<ApiRequestDTO> apis) {
this.apis = apis;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public List<ApiRequestDTO> getApis() {
return apis;
}
}
@@ -0,0 +1,118 @@
package com.eactive.testmaster.client.dto;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
public class ApiRequestDTO {
private String id;
private String name;
private String path;
private String method;
@NotNull(message = "서버를 선택해주세요.")
private Long server;
private List<ApiRequestKeyValueDTO> headers = new ArrayList<>();
private List<ApiRequestKeyValueDTO> queryParams = new ArrayList<>();
private List<ApiRequestKeyValueDTO> variables = new ArrayList<>();
private String requestBody;
private String preRequestScript;
private String postRequestScript;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Long getServer() {
return server;
}
public void setServer(Long server) {
this.server = server;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public List<ApiRequestKeyValueDTO> getHeaders() {
return headers;
}
public void setHeaders(List<ApiRequestKeyValueDTO> headers) {
this.headers = headers;
}
public List<ApiRequestKeyValueDTO> getQueryParams() {
return queryParams;
}
public void setQueryParams(List<ApiRequestKeyValueDTO> queryParams) {
this.queryParams = queryParams;
}
public List<ApiRequestKeyValueDTO> getVariables() {
return variables;
}
public void setVariables(List<ApiRequestKeyValueDTO> variables) {
this.variables = variables;
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
public String getPreRequestScript() {
return preRequestScript;
}
public void setPreRequestScript(String preRequestScript) {
this.preRequestScript = preRequestScript;
}
public String getPostRequestScript() {
return postRequestScript;
}
public void setPostRequestScript(String postRequestScript) {
this.postRequestScript = postRequestScript;
}
}
@@ -0,0 +1,37 @@
package com.eactive.testmaster.client.dto;
public class ApiRequestKeyValueDTO {
private boolean enabled;
private String key;
private String value;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String toString() {
return (enabled?"":"//")+ key + "::" + value;
}
}
@@ -0,0 +1,46 @@
package com.eactive.testmaster.client.dto;
import java.util.List;
public class ApiResponse {
private int status;
private List<ApiRequestKeyValueDTO> headers;
private String body;
private int size;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public List<ApiRequestKeyValueDTO> getHeaders() {
return headers;
}
public void setHeaders(List<ApiRequestKeyValueDTO> headers) {
this.headers = headers;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
}
@@ -0,0 +1,44 @@
package com.eactive.testmaster.client.dto;
public class ApiScenarioDTO {
private String id;
private String name;
private String description;
private String scenario;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getScenario() {
return scenario;
}
public void setScenario(String scenario) {
this.scenario = scenario;
}
}
@@ -0,0 +1,61 @@
package com.eactive.testmaster.client.entity;
import com.eactive.testmaster.user.entity.StaffUser;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "API_COLLECTION")
public class ApiCollection {
@Id
private String id; //uuid
private String name;
@ManyToOne
@JoinColumn(name = "USER_ID")
private StaffUser owner;
@ManyToMany
@JoinTable(
name = "API_COLLECTION_API_REQUEST_INFO",
joinColumns = @JoinColumn(name = "API_COLLECTION_ID"),
inverseJoinColumns = @JoinColumn(name = "API_REQUEST_INFO_ID")
)
private List<ApiRequestInfo> apis = new ArrayList<>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<ApiRequestInfo> getApis() {
return apis;
}
public void setApis(List<ApiRequestInfo> apis) {
this.apis = apis;
}
public StaffUser getOwner() {
return owner;
}
public void setOwner(StaffUser owner) {
this.owner = owner;
}
}
@@ -0,0 +1,117 @@
package com.eactive.testmaster.client.entity;
import com.eactive.testmaster.server.entity.Server;
import com.eactive.testmaster.user.entity.StaffUser;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.time.LocalDateTime;
public class ApiRequestHistory {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToOne
@JoinColumn(name = "SERVER_ID")
private Server server;
@ManyToOne
@JoinColumn(name = "USER_ID")
private StaffUser owner;
private String method;
private String uri;
private String requestHeaders;
private String requestBody;
private String responseBody;
private String responseHeaders;
@Type(type = "org.hibernate.type.LocalDateTimeType")
private LocalDateTime requestTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Server getServer() {
return server;
}
public void setServer(Server server) {
this.server = server;
}
public StaffUser getOwner() {
return owner;
}
public void setOwner(StaffUser owner) {
this.owner = owner;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getRequestHeaders() {
return requestHeaders;
}
public void setRequestHeaders(String requestHeaders) {
this.requestHeaders = requestHeaders;
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
public String getResponseBody() {
return responseBody;
}
public void setResponseBody(String responseBody) {
this.responseBody = responseBody;
}
public String getResponseHeaders() {
return responseHeaders;
}
public void setResponseHeaders(String responseHeaders) {
this.responseHeaders = responseHeaders;
}
public LocalDateTime getRequestTime() {
return requestTime;
}
public void setRequestTime(LocalDateTime requestTime) {
this.requestTime = requestTime;
}
}
@@ -0,0 +1,159 @@
package com.eactive.testmaster.client.entity;
import com.eactive.testmaster.server.entity.Server;
import com.eactive.testmaster.user.entity.StaffUser;
import javax.persistence.*;
import java.util.Objects;
@Entity
@Table(name = "API_REQUEST_INFO")
public class ApiRequestInfo {
@Id
private String id; //uuid
@ManyToOne
@JoinColumn(name = "SERVER_ID")
private Server server;
@ManyToOne
@JoinColumn(name = "USER_ID")
private StaffUser owner;
private String name;
private String method;
private String path;
@Lob
private String headers;
@Lob
private String queryParams;
@Lob
private String variables;
@Lob
private String requestBody;
@Lob
private String preRequestScript;
@Lob
private String postRequestScript;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Server getServer() {
return server;
}
public void setServer(Server server) {
this.server = server;
}
public StaffUser getOwner() {
return owner;
}
public void setOwner(StaffUser owner) {
this.owner = owner;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getHeaders() {
return headers;
}
public void setHeaders(String headers) {
this.headers = headers;
}
public String getQueryParams() {
return queryParams;
}
public void setQueryParams(String queryParams) {
this.queryParams = queryParams;
}
public String getVariables() {
return variables;
}
public void setVariables(String variables) {
this.variables = variables;
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
public String getPreRequestScript() {
return preRequestScript;
}
public void setPreRequestScript(String preRequestScript) {
this.preRequestScript = preRequestScript;
}
public String getPostRequestScript() {
return postRequestScript;
}
public void setPostRequestScript(String postRequestScript) {
this.postRequestScript = postRequestScript;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ApiRequestInfo that = (ApiRequestInfo) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id, server, owner, method, path, headers, queryParams, requestBody, preRequestScript, postRequestScript);
}
}
@@ -0,0 +1,64 @@
package com.eactive.testmaster.client.entity;
import com.eactive.testmaster.user.entity.StaffUser;
import javax.persistence.*;
@Entity
@Table(name = "API_SCENARIO")
public class ApiScenario {
@Id
private String id; //uuid
private String name;
private String description;
@ManyToOne
@JoinColumn(name = "USER_ID")
private StaffUser owner;
@Lob
private String scenario;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getScenario() {
return scenario;
}
public void setScenario(String scenario) {
this.scenario = scenario;
}
public StaffUser getOwner() {
return owner;
}
public void setOwner(StaffUser owner) {
this.owner = owner;
}
}
@@ -0,0 +1,33 @@
package com.eactive.testmaster.client.mapper;
import com.eactive.testmaster.client.dto.ApiCollectionDTO;
import com.eactive.testmaster.client.dto.ApiRequestDTO;
import com.eactive.testmaster.client.entity.ApiCollection;
import com.eactive.testmaster.client.entity.ApiRequestInfo;
import com.eactive.testmaster.common.mapper.CommonMapper;
import com.eactive.testmaster.server.mapper.ServerMapper;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;
import java.util.List;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class, ServerMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public interface ApiRequestMapper {
ApiCollection map(ApiCollectionDTO dto);
ApiRequestInfo map(ApiRequestDTO dto);
ApiRequestDTO map(ApiRequestInfo entity);
List<ApiRequestInfo> mapApis(List<ApiRequestDTO> apis);
List<ApiCollectionDTO> mapCollections(List<ApiCollection> apis);
}
@@ -0,0 +1,21 @@
package com.eactive.testmaster.client.mapper;
import com.eactive.testmaster.client.dto.ApiScenarioDTO;
import com.eactive.testmaster.client.entity.ApiScenario;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;
import java.util.List;
@Mapper(componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public interface ApiScenarioMapper {
ApiScenario map(ApiScenarioDTO dto);
ApiScenarioDTO map(ApiScenario scenario);
List<ApiScenarioDTO> toDtoList(List<ApiScenario> scenarios);
}
@@ -0,0 +1,13 @@
package com.eactive.testmaster.client.repository;
import com.eactive.testmaster.client.entity.ApiCollection;
import com.eactive.testmaster.user.entity.StaffUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
public interface ApiCollectionRepository extends JpaRepository<ApiCollection, String>, JpaSpecificationExecutor<ApiCollection> {
List<ApiCollection> findAllByOwner(StaffUser owner);
}
@@ -0,0 +1,9 @@
package com.eactive.testmaster.client.repository;
import com.eactive.testmaster.client.entity.ApiRequestInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ApiRequestRepository extends JpaRepository<ApiRequestInfo, String>, JpaSpecificationExecutor<ApiRequestInfo> {
}
@@ -0,0 +1,13 @@
package com.eactive.testmaster.client.repository;
import com.eactive.testmaster.client.entity.ApiScenario;
import com.eactive.testmaster.user.entity.StaffUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
public interface ApiScenarioRepository extends JpaRepository<ApiScenario, String>, JpaSpecificationExecutor<ApiScenario> {
List<ApiScenario> findAllByOwner(StaffUser owner);
}
@@ -0,0 +1,98 @@
package com.eactive.testmaster.client.service;
import com.eactive.testmaster.client.entity.ApiCollection;
import com.eactive.testmaster.client.entity.ApiRequestInfo;
import com.eactive.testmaster.client.repository.ApiCollectionRepository;
import com.eactive.testmaster.client.repository.ApiRequestRepository;
import com.eactive.testmaster.common.exception.NotFoundException;
import com.eactive.testmaster.user.entity.StaffUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils;
import javax.transaction.Transactional;
import java.util.List;
import java.util.UUID;
@Service
@Transactional
public class ApiRequestMgmtService {
private static final String API_COLLECTION_NOT_FOUND = "api collection[%s] not found";
private static final String API_NOT_FOUND = "api collection[%s] not found";
@Autowired
ApiCollectionRepository apiCollectionRepository;
@Autowired
ApiRequestRepository apiRequestRepository;
public List<ApiCollection> getMyApis(StaffUser user) {
return apiCollectionRepository.findAllByOwner(user);
}
public void deleteApiCollection(String id) {
ApiCollection collection = apiCollectionRepository.findById(id).orElseThrow(() -> new NotFoundException(String.format(API_COLLECTION_NOT_FOUND, id)));
if (!collection.getApis().isEmpty()) {
throw new RuntimeException("api collection[" + id + "] is not empty");
}
apiCollectionRepository.deleteById(id);
}
public void createNewApiCollection(ApiCollection collection) {
collection.setId(UUID.randomUUID().toString());
apiCollectionRepository.save(collection);
}
public void updateApiCollectionName(String id, ApiCollection updated) {
ApiCollection collection = apiCollectionRepository.findById(id).orElseThrow(() -> new NotFoundException(String.format(API_COLLECTION_NOT_FOUND, id)));
collection.setName(updated.getName());
apiCollectionRepository.save(collection);
}
public String saveApiToCollection(String id, ApiRequestInfo api) {
ApiCollection collection = apiCollectionRepository.findById(id).orElseThrow(() -> new NotFoundException(String.format(API_COLLECTION_NOT_FOUND, id)));
if (StringUtils.isEmpty(api.getId())) {
api.setId(UUID.randomUUID().toString());
apiRequestRepository.save(api);
} else {
String apiId = api.getId();
ApiRequestInfo existing = apiRequestRepository.findById(apiId).orElseThrow(() -> new NotFoundException(String.format(API_NOT_FOUND, apiId)));
existing.setName(api.getName());
existing.setMethod(api.getMethod());
existing.setPath(api.getPath());
existing.setHeaders(api.getHeaders());
existing.setQueryParams(api.getQueryParams());
existing.setVariables(api.getVariables());
existing.setServer(api.getServer());
existing.setRequestBody(api.getRequestBody());
existing.setPreRequestScript(api.getPreRequestScript());
existing.setPostRequestScript(api.getPostRequestScript());
apiRequestRepository.save(existing);
api = existing;
}
if (!collection.getApis().contains(api)) {
collection.getApis().add(api);
apiCollectionRepository.save(collection);
}
return api.getId();
}
public void removeApiFromCollection(String id, String apiId) {
ApiCollection collection = apiCollectionRepository.findById(id).orElseThrow(() -> new NotFoundException(String.format(API_COLLECTION_NOT_FOUND, id)));
ApiRequestInfo api = apiRequestRepository.findById(apiId).orElseThrow(() -> new NotFoundException(String.format(API_NOT_FOUND, apiId)));
collection.getApis().remove(api);
apiCollectionRepository.save(collection);
apiRequestRepository.deleteById(apiId);
}
}
@@ -0,0 +1,58 @@
package com.eactive.testmaster.client.service;
import com.eactive.testmaster.client.repository.ApiRequestRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import javax.transaction.Transactional;
import java.sql.Connection;
import java.sql.Statement;
@Service
@Transactional
public class ApiRequestMigrationService {
@Autowired
private ApplicationContext applicationContext;
@Autowired
ApiRequestRepository apiRequestRepository;
@EventListener(ApplicationReadyEvent.class)
public void migrate() {
// Fetch DataSource from Spring Context
DataSource dataSource = applicationContext.getBean(DataSource.class);
try (Connection connection = dataSource.getConnection()) {
String sql1 = String.format("ALTER TABLE %s ALTER COLUMN %s %s;", "API_REQUEST_INFO", "HEADERS", "CLOB");
try (Statement statement = connection.createStatement()) {
statement.execute(sql1);
}
String sql2 = String.format("ALTER TABLE %s ALTER COLUMN %s %s;", "API_REQUEST_INFO", "QUERY_PARAMS", "CLOB");
try (Statement statement = connection.createStatement()) {
statement.execute(sql2);
}
String sql3 = String.format("ALTER TABLE %s ALTER COLUMN %s %s;", "API_REQUEST_INFO", "REQUEST_BODY", "CLOB");
try (Statement statement = connection.createStatement()) {
statement.execute(sql3);
}
} catch (Exception e) {
e.printStackTrace();
}
apiRequestRepository.findAll().forEach(apiRequest -> {
apiRequest.setHeaders(apiRequest.getHeaders().replace("=", "::"));
apiRequest.setQueryParams(apiRequest.getQueryParams().replace("=", "::"));
apiRequestRepository.save(apiRequest);
});
}
}
@@ -0,0 +1,49 @@
package com.eactive.testmaster.client.service;
import com.eactive.testmaster.client.entity.ApiScenario;
import com.eactive.testmaster.client.repository.ApiScenarioRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
import java.util.UUID;
@Service
@Transactional
public class ApiScenarioMgmtService {
@Autowired
ApiScenarioRepository apiScenarioRepository;
public List<ApiScenario> findAll() {
return apiScenarioRepository.findAll();
}
public ApiScenario createApiScenario(ApiScenario apiScenario) {
apiScenario.setId(UUID.randomUUID().toString());
return apiScenarioRepository.save(apiScenario);
}
public ApiScenario updateApiScenario(String id, ApiScenario updatedApiScenario) {
return apiScenarioRepository.findById(id)
.map(apiScenario -> {
apiScenario.setName(updatedApiScenario.getName());
apiScenario.setDescription(updatedApiScenario.getDescription());
apiScenario.setScenario(updatedApiScenario.getScenario());
return apiScenarioRepository.save(apiScenario);
}).orElseGet(() -> {
// If the ApiScenario with the given id doesn't exist, create a new one
updatedApiScenario.setId(id);
return apiScenarioRepository.save(updatedApiScenario);
});
}
// Delete operation
public void deleteApiScenario(String id) {
apiScenarioRepository.deleteById(id);
}
}
@@ -0,0 +1,69 @@
package com.eactive.testmaster.client.service;
import com.eactive.testmaster.client.dto.ApiRequestKeyValueDTO;
import com.eactive.testmaster.client.dto.ApiResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.util.CharsetUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class HttpHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
private final CompletableFuture<ApiResponse> responseFuture;
public HttpHandler(CompletableFuture<ApiResponse> responseFuture) {
this.responseFuture = responseFuture;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse response) {
ApiResponse apiResponse = new ApiResponse();
apiResponse.setStatus(response.status().code());
apiResponse.setHeaders(convertHttpHeadersToList(response.headers()));
apiResponse.setBody(response.content().toString(CharsetUtil.UTF_8));
int headerSize = calculateHeaderSize(response.headers());
int contentSize = response.content().readableBytes();
int totalSize = headerSize + contentSize;
apiResponse.setSize(totalSize);
responseFuture.complete(apiResponse);
}
private int calculateHeaderSize(HttpHeaders headers) {
int headerSize = 0;
for (Map.Entry<String, String> entry : headers.entries()) {
headerSize += entry.getKey().length() + entry.getValue().length() + 4; // Add 2 for ': ' and 2 for '\r\n'
}
// Add 2 for the final '\r\n' after the headers
headerSize += 2;
return headerSize;
}
public List<ApiRequestKeyValueDTO> convertHttpHeadersToList(HttpHeaders headers) {
List<ApiRequestKeyValueDTO> headerList = new ArrayList<>();
for (Map.Entry<String, String> entry : headers.entries()) {
ApiRequestKeyValueDTO header = new ApiRequestKeyValueDTO();
header.setKey(entry.getKey());
header.setValue(entry.getValue());
headerList.add(header);
}
return headerList;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
responseFuture.completeExceptionally(cause);
ctx.close();
}
}
@@ -0,0 +1,108 @@
package com.eactive.testmaster.client.service;
import com.eactive.testmaster.client.dto.ApiRequestDTO;
import com.eactive.testmaster.client.dto.ApiRequestKeyValueDTO;
import com.eactive.testmaster.client.dto.ApiResponse;
import com.eactive.testmaster.common.exception.ServerNotFoundException;
import com.eactive.testmaster.server.entity.Server;
import com.eactive.testmaster.server.entity.ServerRepository;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.net.ssl.SSLException;
import java.util.concurrent.CompletableFuture;
@Service
public class NettyApiClient {
@Autowired
ServerRepository serverRepository;
public CompletableFuture<ApiResponse> handleRequest(ApiRequestDTO originalRequest) {
ApiRequestDTO apiRequest = originalRequest;
Server server = serverRepository.findById(apiRequest.getServer()).orElseThrow(() -> new ServerNotFoundException("Server not found"));
String host = server.getHostname();
int port = server.getPort();
CompletableFuture<ApiResponse> responseFuture = new CompletableFuture<>();
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws SSLException {
if (server.getScheme().equalsIgnoreCase("https")) {
final SslContext sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc(), host, port));// Add SSL handler
}
ch.pipeline().addLast(new HttpClientCodec());
ch.pipeline().addLast(new HttpObjectAggregator(65536));
ch.pipeline().addLast(new HttpContentDecompressor());
ch.pipeline().addLast(new HttpHandler(responseFuture));
}
});
Channel ch = b.connect(host, port).sync().channel();
ByteBuf content = null;
if (StringUtils.isNotEmpty(apiRequest.getRequestBody())) {
content = Unpooled.copiedBuffer(apiRequest.getRequestBody(), CharsetUtil.UTF_8);
} else {
content = Unpooled.EMPTY_BUFFER;
}
String fullPath = server.getBasePath() + apiRequest.getPath();
HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(apiRequest.getMethod()), fullPath, content);
request.headers().set("Host", host);
request.headers().set("Connection", HttpHeaderValues.CLOSE);
request.headers().set("Accept-Encoding", HttpHeaderValues.GZIP);
if (StringUtils.isNotEmpty(apiRequest.getRequestBody())) {
request.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
}
if (!apiRequest.getHeaders().isEmpty()) {
HttpHeaders customHeaders = new DefaultHttpHeaders();
apiRequest.getHeaders().stream().filter(ApiRequestKeyValueDTO::isEnabled).forEach(header -> {
if (StringUtils.isNotEmpty(header.getKey()) && StringUtils.isNotEmpty(header.getValue())) {
customHeaders.set(header.getKey(), header.getValue());
}
});
request.headers().add(customHeaders);
}
ch.writeAndFlush(request);
ch.closeFuture().sync();
} catch (Exception e) {
ApiResponse response = new ApiResponse();
response.setBody("An error occurred: " + e.getMessage());
return CompletableFuture.completedFuture(response);
} finally {
group.shutdownGracefully();
}
return responseFuture;
}
}
@@ -0,0 +1,83 @@
package com.eactive.testmaster.common.entity;
import org.hibernate.annotations.Type;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Auditable {
/**
* 최초등록시점
*/
@Column(name = "REGISTERED_ON")
@Type(type = "org.hibernate.type.LocalDateTimeType")
@CreatedDate
private LocalDateTime createdAt;
/**
* 최초등록자ID
*/
@Column(name = "REGISTERED_BY")
@CreatedBy
private String createdBy;
/**
* 최종수정시점
*/
@Column(name = "LAST_MODIFIED_ON")
@Type(type = "org.hibernate.type.LocalDateTimeType")
@LastModifiedDate
private LocalDateTime updatedAt;
/**
* 최종수정자ID
*/
@Column(name = "LAST_MODIFIED_BY")
@LastModifiedBy
private String updatedBy;
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public String getUpdatedBy() {
return updatedBy;
}
public void setUpdatedBy(String updatedBy) {
this.updatedBy = updatedBy;
}
}
@@ -0,0 +1,12 @@
package com.eactive.testmaster.common.entity;
public enum EnabledStatus {
Y("사용"),
N("미사용");
private String description;
EnabledStatus(String description) {
this.description = description;
}
}
@@ -0,0 +1,34 @@
package com.eactive.testmaster.common.exception;
import java.time.LocalDateTime;
public class CustomErrorResponse {
private LocalDateTime timestamp;
private String error;
private int status;
public LocalDateTime getTimestamp() {
return timestamp;
}
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
@@ -0,0 +1,45 @@
package com.eactive.testmaster.common.exception;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.time.LocalDateTime;
@Order(1)
@RestControllerAdvice(annotations = RestController.class)
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<CustomErrorResponse> customHandleNotFound(Exception ex, WebRequest request) {
CustomErrorResponse errors = new CustomErrorResponse();
errors.setTimestamp(LocalDateTime.now());
errors.setError(ex.getMessage());
errors.setStatus(HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<CustomErrorResponse> customHandleRuntime(Exception ex, WebRequest request) {
CustomErrorResponse errors = new CustomErrorResponse();
errors.setTimestamp(LocalDateTime.now());
errors.setError(ex.getMessage());
errors.setStatus(HttpStatus.BAD_REQUEST.value());
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(UserNotLoginException.class)
public ResponseEntity<CustomErrorResponse> customHandleUserNotLogin(Exception ex, WebRequest request) {
CustomErrorResponse errors = new CustomErrorResponse();
errors.setTimestamp(LocalDateTime.now());
errors.setError(ex.getMessage());
errors.setStatus(HttpStatus.UNAUTHORIZED.value());
return new ResponseEntity<>(errors, HttpStatus.UNAUTHORIZED);
}
}
@@ -0,0 +1,7 @@
package com.eactive.testmaster.common.exception;
public class NotFoundException extends IllegalArgumentException {
public NotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,39 @@
package com.eactive.testmaster.common.exception;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
@Controller
public class PortalErrorController implements ErrorController {
@GetMapping("/error")
public String handleError(HttpServletRequest request, ModelMap model) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
model.addAttribute("status", status);
return "error";
}
return "redirect:/";
}
@PostMapping("/error")
public String handleErrorPost(HttpServletRequest request, ModelMap model) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
model.addAttribute("status", status);
return "error";
}
return "redirect:/";
}
}
@@ -0,0 +1,34 @@
package com.eactive.testmaster.common.exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice(annotations = Controller.class)
@Order(2)
public class PortalGlobalExceptionHandler {
private final Logger log = LoggerFactory.getLogger(getClass());
@ExceptionHandler(value = NotFoundException.class)
public ModelAndView handleIllegalArgumentException(HttpServletRequest request, NotFoundException ex) {
log.error(ex.getMessage());
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("exception", ex);
return modelAndView;
}
@ExceptionHandler(value = UserNotLoginException.class)
public ModelAndView handleIllegalArgumentException(HttpServletRequest request, UserNotLoginException ex) {
return new ModelAndView("redirect:/login");
}
}
@@ -0,0 +1,8 @@
package com.eactive.testmaster.common.exception;
public class ServerNotFoundException extends RuntimeException {
public ServerNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package com.eactive.testmaster.common.exception;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,8 @@
package com.eactive.testmaster.common.exception;
public class UserNotLoginException extends RuntimeException {
public UserNotLoginException() {
super("User not login");
}
}
@@ -0,0 +1,8 @@
package com.eactive.testmaster.common.exception;
public class UserPasswordResetException extends RuntimeException {
public UserPasswordResetException(String message) {
super(message);
}
}
@@ -0,0 +1,75 @@
package com.eactive.testmaster.common.interceptor;
import com.eactive.testmaster.common.exception.UserNotLoginException;
import com.eactive.testmaster.common.util.SecurityUtil;
import com.eactive.testmaster.user.entity.AnonymousUser;
import com.eactive.testmaster.user.entity.User;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 인증여부 체크 인터셉터
*
* @author
* @version 1.0
*/
public class AuthenticInterceptor implements AsyncHandlerInterceptor {
/**
* 관리자 접근 권한 패턴 목록
*/
private List<String> adminAuthPatternList;
private static AntPathMatcher pathMatcher = new AntPathMatcher();
public List<String> getAdminAuthPatternList() {
return adminAuthPatternList;
}
public void setAdminAuthPatternList(List<String> adminAuthPatternList) {
this.adminAuthPatternList = adminAuthPatternList;
}
/**
* 인증된 사용자 여부로 인증 여부를 체크한다.
* 관리자 권한에 따라 접근 페이지 권한을 체크한다.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//인증된사용자 여부
User currentUser = SecurityUtil.getCurrentUser();
if (currentUser == null || currentUser instanceof AnonymousUser) {
throw new UserNotLoginException();
}
boolean found = findPattern(request.getRequestURI());
if (found) {
if (currentUser.getUserSecurity() != null && "ROLE_ADMIN".equals(currentUser.getUserSecurity())) {
return true;
} else {
throw new UserNotLoginException();
}
}
return true;
}
boolean findPattern(String path) {
for (String pattern : adminAuthPatternList) {
if (pathMatcher.match(pattern, path)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,81 @@
package com.eactive.testmaster.common.mapper;
import com.eactive.testmaster.client.dto.ApiRequestKeyValueDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Named;
import org.mapstruct.ReportingPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Mapper(componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE)
public class CommonMapper {
public String map(boolean value) {
return value ? "Y" : "N";
}
public boolean map(String value) {
if (value == null) return false;
return value.equals("Y");
}
public String map(List<String> values) {
if (values == null) return "";
return String.join(",", values);
}
public List<String> toList(String value) {
if (StringUtils.hasLength(value)) return new ArrayList<>();
return Arrays.asList(value.split(","));
}
@Named("authorities")
public List<String> authorities(Collection<? extends GrantedAuthority> grantedAuthorities) {
return grantedAuthorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
}
public String mapKeyValue(List<ApiRequestKeyValueDTO> list) {
if (list == null) return "";
StringBuilder result = new StringBuilder();
for (ApiRequestKeyValueDTO dto : list) {
result.append(dto.toString()).append("||");
}
if (result.length() > 0) {
result.setLength(result.length() - 2); // Remove the last "||"
}
return result.toString();
}
public List<ApiRequestKeyValueDTO> keyValueToList(String keyValue) {
List<ApiRequestKeyValueDTO> list = new ArrayList<>();
if (!StringUtils.hasLength(keyValue)) return list;
String[] pairs = keyValue.split("\\|\\|");
for (String pair : pairs) {
ApiRequestKeyValueDTO dto = new ApiRequestKeyValueDTO();
if (pair.startsWith("//")) {
dto.setEnabled(false);
pair = pair.substring(2);
} else {
dto.setEnabled(true);
}
String[] keyValueArray = pair.split("::");
if (keyValueArray.length == 2) {
dto.setKey(keyValueArray[0]);
dto.setValue(keyValueArray[1]);
list.add(dto);
}
}
return list;
}
}
@@ -0,0 +1,56 @@
package com.eactive.testmaster.common.message;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PortalReloadableResourceBundleMessageSource extends org.springframework.context.support.ReloadableResourceBundleMessageSource {
private static final String CLASSPATH_PREFIX = "classpath:";
private static final String PROPERTIES_EXTENSION = ".properties";
private final ResourcePatternResolver resourcePatternResolver;
public PortalReloadableResourceBundleMessageSource() {
this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
}
public void setEgovBaseNames(String... basenames) {
List<String> resolvedBasenames = new ArrayList<>();
for (String basename : basenames) {
String trimmedBasename = StringUtils.trimToEmpty(basename);
if (trimmedBasename.startsWith(CLASSPATH_PREFIX)) {
resolvedBasenames.add(trimmedBasename);
} else if (StringUtils.isNotBlank(trimmedBasename)) {
try {
Resource[] resources = resourcePatternResolver.getResources(trimmedBasename);
for (Resource resource : resources) {
String uri = resource.getURI().toString();
if (!uri.contains(PROPERTIES_EXTENSION)) {
continue;
}
String baseName = processBasename(uri);
resolvedBasenames.add(baseName);
}
} catch (IOException e) {
logger.debug("No message source files found for basename " + trimmedBasename + ".");
}
}
}
setBasenames(resolvedBasenames.toArray(new String[0]));
}
private String processBasename(String uri) {
String baseName = StringUtils.substringBeforeLast(uri, PROPERTIES_EXTENSION);
String prefix = StringUtils.substringBeforeLast(baseName, "/");
String name = StringUtils.substringAfterLast(baseName, "/");
while (name.contains("_")) {
name = StringUtils.substringBeforeLast(name, "_");
}
return prefix + "/" + name;
}
}
@@ -0,0 +1,90 @@
package com.eactive.testmaster.common.search;
import org.springframework.data.jpa.domain.Specification;
import java.util.List;
/**
* Overview:
*
* BaseSearch 클래스는 Spring Data JPA 프레임워크를 사용하여 복잡한 검색 쿼리를 편리하게 작성할 수 있는 방법을 제공하는 추상 클래스입니다.
* 이 클래스를 사용하면 SearchModel 객체 목록을 사용하여 검색 쿼리를 생성할 수 있으며, 이를 결합하여 보다 복잡하고 구체적인 검색 조건을 만들 수 있습니다.
*
* How to Use:
*
* BaseSearch 클래스를 사용하려면 검색하려는 엔티티에 특정한 BaseSearch의 하위 클래스를 만들어야 합니다.
* 예를 들어 User 개체를 검색하려는 경우 BaseSearch<User>를 확장하는 UserSearch라는 클래스를 만들 수 있습니다.
*
* 하위 클래스에서 buildSearchCondition() 메서드를 구현해야 합니다.
* 이 메서드는 사용하려는 검색 조건을 나타내는 SearchModel 객체 목록을 반환해야 합니다.
*
* 예를 들어, 다음은 이름에 특정 문자열이 포함된 사용자를 검색하기 위해 buildSearchCondition() 메서드를 구현하는 방법입니다:
*
* public List<SearchModel> buildSearchCondition() {
* List<SearchModel> models = new ArrayList<>();
* models.add(new ColumnSearchModel("firstName", SearchCondition.LIKE, "John"));
* models.add(new ColumnSearchModel("lastName", SearchCondition.LIKE, "Doe"));
* return models;
* }
*
* 이 예제에서는 이름과 성에 각각 문자열 "John"과 "Doe"가 포함된 사용자를 검색하는 두 개의 ColumnSearchModel 객체를 만들고 있습니다.
*
* 검색 쿼리를 빌드하려면 UserSearch 클래스의 인스턴스를 생성하고 그 인스턴스에서 buildSpecification() 메서드를 호출하면 됩니다.
* 이 메서드는 Spring 데이터 JPA를 사용하여 데이터베이스를 쿼리하는 데 사용할 수 있는 Specification 객체를 반환합니다.
*
* UserSearch search = new UserSearch();
* Specification<User> spec = search.buildSpecification();
* List<User> users = userRepository.findAll(spec);
*
* 이 예제에서는 UserSearch 인스턴스를 만들고 buildSpecification() 메서드를 호출하여 사양 객체를 빌드한 다음 이 사양 객체를 사용하여 데이터베이스에서 검색 조건과 일치하는 모든 사용자 객체를 쿼리합니다.
* 결과는 User 객체 목록으로 반환됩니다.
*
* 추가 활용 방법
*
* BaseSearch 클래스는 유연하고 확장 가능하도록 설계되어 필요에 따라 더 복잡한 검색 쿼리를 사용할 수 있습니다.
* getSpecification() 메서드를 구현하는 SearchModel의 새로운 서브클래스를 생성하여 검색 조건을 추가할 수 있습니다.
* 필요에 따라 추가 검색 조건을 포함하도록 SpecificationUtil 클래스를 수정할 수도 있습니다.
*
* 또한 BaseSearch 클래스를 확장하여 정렬 및 페이징과 같은 추가 기능을 제공할 수 있습니다.
* 하위 클래스에 메서드와 속성을 추가하여 애플리케이션의 특정 요구 사항을 충족하는 검색 쿼리를 만들 수 있습니다.
*
* ColumnSearchModel은 검색조건이 컬럼명과 비교 연산자, 값으로 구성된다. SearchCondition 참조
* SpecificationSearchModel은 검색조건이 Specification을 리턴하는 형태로 구성된다. 검색조건을 복잡하게 구성해야 할 때 사용한다.
*
* public static Specification current(String user, ApproverStatus approverStatus) {
* return (root, criteriaQuery, criteriaBuilder) -> {
* ListJoin join = root.joinList("approvers");
* return criteriaBuilder.and(
* criteriaBuilder.equal(join.get("user"), user),
* criteriaBuilder.equal(join.get("status"), approverStatus));
* };
* }
*/
public interface BaseSearch<T> {
List<SearchModel> buildSearchCondition();
default Specification<T> buildSpecification() {
List<SearchModel> models = buildSearchCondition();
Specification<T> specifications = null;
for (SearchModel model : models) {
Specification spec = getSpecification(model);
if (spec != null) {
if (specifications == null) {
specifications = Specification.where(spec);
} else {
specifications = specifications.and(spec);
}
}
}
return specifications;
}
default Specification<T> getSpecification(SearchModel model) {
return model.getSpecification();
}
}
@@ -0,0 +1,65 @@
package com.eactive.testmaster.common.search;
import com.eactive.testmaster.common.util.SpecificationUtil;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.ObjectUtils;
import java.util.Collection;
public class ColumnSearchModel implements SearchModel {
private String column;
private Object value; //값의 형태는 String, Collection, Object 등이 될 수 있다.
private SearchCondition condition;
public ColumnSearchModel(String column, SearchCondition condition, Object value) {
this.column = column;
this.condition = condition;
this.value = value;
}
public String getColumn() {
return column;
}
public void setColumn(String column) {
this.column = column;
}
public SearchCondition getCondition() {
return condition;
}
public void setCondition(SearchCondition condition) {
this.condition = condition;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public Specification getSpecification() {
if (condition == null) return null;
if (column == null) return null;
if (ObjectUtils.isEmpty(value)) return null;
if (condition.equals(SearchCondition.EQUAL)) {
return SpecificationUtil.equalTo(column, value);
} else if (condition.equals(SearchCondition.LIKE)) {
return SpecificationUtil.like(column, value.toString());
} else if (condition.equals(SearchCondition.IN)) {
return SpecificationUtil.in(column, (Collection) value);
} else if (condition.equals(SearchCondition.NOT_EQUAL)) {
return SpecificationUtil.notEqualTo(column, value);
}
return null;
}
}
@@ -0,0 +1,8 @@
package com.eactive.testmaster.common.search;
public enum SearchCondition {
EQUAL,
LIKE,
IN,
NOT_EQUAL
}
@@ -0,0 +1,10 @@
package com.eactive.testmaster.common.search;
import org.springframework.data.jpa.domain.Specification;
import java.io.Serializable;
public interface SearchModel extends Serializable {
Specification getSpecification();
}
@@ -0,0 +1,17 @@
package com.eactive.testmaster.common.search;
import org.springframework.data.jpa.domain.Specification;
public class SpecificationSearchModel implements SearchModel {
private final Specification specification;
public SpecificationSearchModel(Specification specification) {
this.specification = specification;
}
public Specification getSpecification() {
return specification;
}
}
@@ -0,0 +1,36 @@
package com.eactive.testmaster.common.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* ApplicationContextUtil 클래스
*
* @author
* @version 1.0
*/
@Component("ApplicationContextUtil")
public class ApplicationContextUtil implements ApplicationContextAware {
public static ApplicationContext getContext() {
return context;
}
public static void setContext(ApplicationContext context) {
ApplicationContextUtil.context = context;
}
private static ApplicationContext context;
@SuppressWarnings("static-access")
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}
}
@@ -0,0 +1,62 @@
package com.eactive.testmaster.common.util;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @author
* @version 1.0
* @Class Name : EncryptionUtil.java
* @Description : 문자열 암호화 처리하는 유틸리티 클래스
* @since 2009.08.26
*/
@Component
public class EncryptionUtil {
private static final String SHA_256 = "SHA-256";
MessageDigest md;
public EncryptionUtil() throws NoSuchAlgorithmException {
md = MessageDigest.getInstance(SHA_256);
}
public String generateNewPassword() {
// 2. 임시 비밀번호를 생성한다.(영+영+숫+영+영+숫+영+영=8자리)
StringBuilder newpassword = new StringBuilder();
for (int i = 1; i <= 8; i++) {
// 영자
if (i % 3 != 0) {
newpassword.append(RandomUtil.getRandomStr('a', 'z'));
// 숫자
} else {
newpassword.append(RandomUtil.getRandomNum(0, 9));
}
}
return newpassword.toString();
}
/**
* 비밀번호를 암호화하는 기능(복호화가 되면 안되므로 SHA-256 인코딩 방식 적용)
*
* @param password 암호화될 패스워드
* @param id salt로 사용될 사용자 ID 지정
* @return 암호화된 패스워드
*/
public synchronized String encryptPassword(String password, String id) {
if (StringUtils.isEmpty(password)) return "";
if (StringUtils.isEmpty(id)) return "";
byte[] hashValue = null; // 해쉬값
md.reset();
md.update(id.getBytes());
hashValue = md.digest(password.getBytes());
return new String(Base64.encodeBase64(hashValue));
}
}
@@ -0,0 +1,58 @@
package com.eactive.testmaster.common.util;
import java.security.SecureRandom;
public class RandomUtil {
private RandomUtil() {
throw new IllegalStateException("Utility class");
}
public static int getRandomNum(int startNum, int endNum) {
int randomNum = 0;
// 랜덤 객체 생성
SecureRandom rnd = new SecureRandom();
do {
// 종료숫자내에서 랜덤 숫자를 발생시킨다.
randomNum = rnd.nextInt(endNum + 1);
} while (randomNum < startNum); // 랜덤 숫자가 시작숫자보다 작을경우 다시 랜덤숫자를 발생시킨다.
return randomNum;
}
/**
* 문자열 A에서 Z사이의 랜덤 문자열을 구하는 기능을 제공 시작문자열과 종료문자열 사이의 랜덤 문자열을 구하는 기능
*
* @param startChr - 첫 문자
* @param endChr - 마지막문자
* @return 랜덤문자
* @see
*/
public static String getRandomStr(char startChr, char endChr) {
int randomInt;
String randomStr = null;
// 시작문자열이 종료문자열보가 클경우
if (startChr > endChr) {
throw new IllegalArgumentException("Start String: " + startChr + " End String: " + endChr);
}
// 랜덤 객체 생성
SecureRandom rnd = new SecureRandom();
do {
// 시작문자 및 종료문자 중에서 랜덤 숫자를 발생시킨다.
randomInt = rnd.nextInt(endChr + 1);
} while (randomInt < startChr); // 입력받은 문자 'A'(65)보다 작으면 다시 랜덤 숫자 발생.
// 랜덤 숫자를 문자로 변환 후 스트링으로 다시 변환
randomStr = (char) randomInt + "";
// 랜덤문자열를 리턴
return randomStr;
}
}
@@ -0,0 +1,33 @@
package com.eactive.testmaster.common.util;
import com.eactive.testmaster.user.entity.AnonymousUser;
import com.eactive.testmaster.user.entity.User;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityUtil {
private SecurityUtil() {
// Utility class
throw new IllegalStateException("Utility class");
}
public static final String ANONYMOUS_USER = "anonymousUser";
public static User getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authentication.getPrincipal().equals(ANONYMOUS_USER)) {
return ((User) authentication.getDetails());
}
return new AnonymousUser();
}
public static String getCurrentUserEsntlId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authentication.getPrincipal().equals(ANONYMOUS_USER)) {
return ((User) authentication.getDetails()).getEsntlId();
}
return "ANONYMOUS";
}
}
@@ -0,0 +1,101 @@
package com.eactive.testmaster.common.util;
import org.hibernate.query.criteria.internal.path.PluralAttributePath;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
import java.util.Collection;
public class SpecificationUtil {
private SpecificationUtil() {
throw new IllegalStateException("Utility class");
}
public static <T> Path getPath(Root<T> root, String column) {
Path p = root;
if (column.contains(".")) {
String[] columns = column.split("\\.");
for (String s : columns) {
Path<Object> c = p.get(s);
if (c instanceof PluralAttributePath) {
p = root.joinList(s);
} else {
p = p.get(s);
}
}
return p;
} else {
return root.get(column);
}
}
public static <T> Specification<T> notNull(String column) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.isNotNull(root.get(column));
}
public static <T> Specification<T> isNull(String column) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.isNull(root.get(column));
}
public static <T> Specification<T> equalTo(String column, Object value) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(getPath(root, column), value);
}
public static <T> Specification<T> equalToJoin(String table, String column, Object value) {
return (root, criteriaQuery, criteriaBuilder) -> {
Join join = root.join(table, JoinType.INNER);
return criteriaBuilder.equal(join.get(column), value);
};
}
public static <T> Specification<T> equalToLeftJoin(String table, String column, Object value) {
return (root, criteriaQuery, criteriaBuilder) -> {
Join join = root.join(table, JoinType.LEFT);
return criteriaBuilder.equal(join.get(column), value);
};
}
public static <T> Specification<T> notEqualTo(String column, Object value) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.notEqual(getPath(root, column), value);
}
public static <T> Specification<T> like(String column, String value) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(criteriaBuilder.lower(getPath(root, column)), "%" + value.toLowerCase() + "%");
}
public static <T> Specification<T> in(String column, Collection<Object> value) {
return (root, criteriaQuery, criteriaBuilder) -> {
CriteriaBuilder.In<Object> in = criteriaBuilder.in(root.get(column));
value.forEach(in::value);
return in;
};
}
public static <T> Specification<T> greater(String column, Object value) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThan(getPath(root, column), (Comparable) value);
}
public static <T> Specification<T> greaterAndEqual(String column, Object value) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(getPath(root, column), (Comparable) value);
}
public static <T> Specification<T> less(String column, Object value) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThan(getPath(root, column), (Comparable) value);
}
public static <T> Specification<T> lessAndEqual(String column, Object value) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(getPath(root, column), (Comparable) value);
}
}
@@ -0,0 +1,21 @@
package com.eactive.testmaster.common.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Constraint(validatedBy = PasswordMatchValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PasswordMatch {
String message() default "{password.not.match}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String input();
String confirm();
}
@@ -0,0 +1,51 @@
package com.eactive.testmaster.common.validator;
import com.eactive.testmaster.common.util.ApplicationContextUtil;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Objects;
public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, Object> {
private String field1;
private String field2;
@Override
public void initialize(PasswordMatch constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
this.field1 = constraintAnnotation.input();
this.field2 = constraintAnnotation.confirm();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object passwordValue = null;
Object password2Value = null;
try {
passwordValue = PropertyUtils.getProperty(value, field1);
password2Value = PropertyUtils.getProperty(value, field2);
} catch (Exception e) {
return false;
}
if (Objects.isNull(passwordValue) || Objects.isNull(password2Value)) {
return false;
}
MessageSource messageSource = (MessageSource) ApplicationContextUtil.getContext().getBean("messageSource");
String message = messageSource.getMessage("password.not.match", null, LocaleContextHolder.getLocale());
if (!passwordValue.equals(password2Value)) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(field2)
.addConstraintViolation();
return false;
}
return true;
}
}
@@ -0,0 +1,17 @@
package com.eactive.testmaster.common.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Constraint(validatedBy = UniqueUserIdValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UniqueId {
String message() default "아이디가 사용중입니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@@ -0,0 +1,22 @@
package com.eactive.testmaster.common.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Constraint(validatedBy = UniqueRouteValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UniqueRoute {
String message() default "등록된 API가 존재합니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String path();
String method();
}
@@ -0,0 +1,49 @@
package com.eactive.testmaster.common.validator;
import com.eactive.testmaster.api.repository.MockRouteRepository;
import com.eactive.testmaster.common.util.ApplicationContextUtil;
import org.apache.commons.beanutils.PropertyUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class UniqueRouteValidator implements ConstraintValidator<UniqueRoute, Object> {
private String method;
private String path;
@Override
public void initialize(UniqueRoute constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
this.method = constraintAnnotation.method();
this.path = constraintAnnotation.path();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
String methodValue;
String pathValue;
try {
methodValue = (String) PropertyUtils.getProperty(value, this.method);
pathValue = (String) PropertyUtils.getProperty(value, this.path);
} catch (Exception e) {
return false;
}
MockRouteRepository mockRouteRepository = ApplicationContextUtil.getContext().getBean(MockRouteRepository.class);
long found = mockRouteRepository.countByMethodAndPathPattern(methodValue, pathValue);
String message = "등록된 API가 존재합니다.";
if (found > 0L){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(this.path)
.addConstraintViolation();
return false;
}
return true;
}
}
@@ -0,0 +1,22 @@
package com.eactive.testmaster.common.validator;
import com.eactive.testmaster.common.exception.UserNotFoundException;
import com.eactive.testmaster.common.util.ApplicationContextUtil;
import com.eactive.testmaster.user.service.UserService;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class UniqueUserIdValidator implements ConstraintValidator<UniqueId, String> {
@Override
public boolean isValid(String id, ConstraintValidatorContext constraintValidatorContext) {
UserService userService = ApplicationContextUtil.getContext().getBean(UserService.class);
try {
userService.findByUserId(id);
return false;
} catch (UserNotFoundException e) {
return true;
}
}
}
@@ -0,0 +1,15 @@
package com.eactive.testmaster.config;
import com.eactive.testmaster.common.util.SecurityUtil;
import org.springframework.data.domain.AuditorAware;
import java.util.Optional;
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.ofNullable(SecurityUtil.getCurrentUserEsntlId());
}
}
@@ -0,0 +1,60 @@
package com.eactive.testmaster.config;
import com.eactive.testmaster.common.entity.EnabledStatus;
import com.eactive.testmaster.common.exception.UserNotFoundException;
import com.eactive.testmaster.login.controller.LoginController;
import com.eactive.testmaster.user.entity.StaffUser;
import com.eactive.testmaster.user.entity.User;
import com.eactive.testmaster.user.repository.StaffUserRepository;
import com.eactive.testmaster.user.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Service
@Transactional
public class PortalAuthenticationFailureHandler implements AuthenticationFailureHandler {
private static final Logger logger = LoggerFactory.getLogger(PortalAuthenticationFailureHandler.class);
UserService userService;
StaffUserRepository staffUserRepository;
public PortalAuthenticationFailureHandler(UserService userService, StaffUserRepository staffUserRepository) {
this.userService = userService;
this.staffUserRepository = staffUserRepository;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor = {AuthenticationException.class, UserNotFoundException.class})
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
String username = request.getParameter("id");
try {
User user = userService.findByUserId(username);
if (user != null) {
user.setLockCnt(user.getLockCnt() + 1);
if (user.getLockCnt() >= 5) {
user.setLockAt(EnabledStatus.Y);
}
staffUserRepository.save((StaffUser) user);
}
} catch (UserNotFoundException e) {
logger.error("{} login try {}", username, e.getMessage());
}
request.getSession().setAttribute(LoginController.LOGIN_MESSAGE, exception.getLocalizedMessage());
String contextPath = request.getContextPath();
response.sendRedirect(contextPath + "/login");
}
}
@@ -0,0 +1,56 @@
package com.eactive.testmaster.config;
import com.eactive.testmaster.common.util.EncryptionUtil;
import com.eactive.testmaster.user.entity.User;
import com.eactive.testmaster.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class PortalAuthenticationManager implements AuthenticationManager {
@Autowired
private UserService userService;
@Autowired
private EncryptionUtil encryptionUtil;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
String username = token.getName();
String password = token.getCredentials().toString();
String encryptPassword = encryptionUtil.encryptPassword(password, username);
UserDetails user = userService.loadUserByUsername(username);
if (!user.isEnabled()) {
throw new DisabledException("계정이 " + ((User) user).getStatus().getDescription() + "상태입니다. 관리자에게 문의하세요.");
}
if (!user.isAccountNonLocked()) {
throw new LockedException("계정이 잠겨있습니다. 관리자에게 문의하세요.");
}
if (user.getPassword().equals(encryptPassword)) {
UsernamePasswordAuthenticationToken authorityToken = new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
authorityToken.setDetails(user);
SecurityContextHolder.getContext().setAuthentication(authorityToken);
return authorityToken;
}
throw new BadCredentialsException("아이디 또는 비밀번호가 일치하지 않습니다.");
}
}
@@ -0,0 +1,43 @@
package com.eactive.testmaster.config;
import com.eactive.testmaster.user.entity.User;
import com.eactive.testmaster.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Service
@Transactional
public class PortalAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
UserService userService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String username = request.getParameter("id");
UserDetails user = userService.loadUserByUsername(username);
((User) user).setLockCnt(0);
userService.updateUser((User) user);
String contextPath = request.getContextPath();
response.sendRedirect(contextPath + "/mgmt/routes/list_view.do");
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication);
}
}
@@ -0,0 +1,37 @@
package com.eactive.testmaster.config;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.envers.repository.support.EnversRevisionRepositoryFactoryBean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
/**
* @author
* @version : 1.0
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ------------- ------------ ---------------------
*
* </pre>
* @ClassName : PortalConfigAppDatasource.java
* @Description : DataSource 설정
* @since :
*/
@Configuration
@EnableJpaRepositories(basePackages = "com.eactive", repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
@EntityScan(basePackages = "com.eactive")
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class PortalConfigAppDatasource {
@Bean
public AuditorAware<String> auditorProvider() {
return new AuditorAwareImpl();
}
}
@@ -0,0 +1,40 @@
package com.eactive.testmaster.config;
import com.eactive.testmaster.common.message.PortalReloadableResourceBundleMessageSource;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/**
* @author
* @version : 1.0
* @ClassName : PortalConfigMessageSource.java
* @Description : Portal Message 설정
* @since : 2021. 7. 20
*/
@Configuration
public class PortalConfigMessageSource {
/**
* @return [Resource 설정] 메세지 Properties 경로 설정
*/
@Bean
public ReloadableResourceBundleMessageSource messageSource() {
PortalReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new PortalReloadableResourceBundleMessageSource();
reloadableResourceBundleMessageSource.setEgovBaseNames(
"classpath*:message/**/*",
"classpath:/org/egovframe/rte/fdl/idgnr/messages/idgnr",
"classpath:/org/egovframe/rte/fdl/property/messages/properties");
return reloadableResourceBundleMessageSource;
}
@Bean
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource);
return factoryBean;
}
}
@@ -0,0 +1,74 @@
package com.eactive.testmaster.config;
import com.navercorp.lucy.security.xss.servletfilter.XssEscapeServletFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class PortalConfigSecurity extends WebSecurityConfigurerAdapter {
private final PortalAuthenticationFailureHandler authenticationFailureHandler;
private final PortalAuthenticationSuccessHandler authenticationSuccessHandler;
private final PortalAuthenticationManager portalAuthenticationManager;
@Autowired
public PortalConfigSecurity(PortalAuthenticationFailureHandler authenticationFailureHandler,
PortalAuthenticationSuccessHandler authenticationSuccessHandler,
PortalAuthenticationManager portalAuthenticationManager) {
this.authenticationFailureHandler = authenticationFailureHandler;
this.authenticationSuccessHandler = authenticationSuccessHandler;
this.portalAuthenticationManager = portalAuthenticationManager;
}
@Bean
public FilterRegistrationBean<XssEscapeServletFilter> xssFilterRegistrationBean() {
FilterRegistrationBean<XssEscapeServletFilter> registrationBean = new FilterRegistrationBean<>();
XssEscapeServletFilter xssEscapeServletFilter = new XssEscapeServletFilter();
registrationBean.setFilter(xssEscapeServletFilter);
registrationBean.setOrder(Integer.MIN_VALUE + 1);
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();//spring security 와 h2-console 같이 사용.
http
.formLogin()
.loginPage("/login")
.usernameParameter("id")
.passwordParameter("password")
.loginProcessingUrl("/actionLogin.do")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.and()
.logout().logoutUrl("/actionLogout.do").logoutSuccessUrl("/")
.and().authorizeRequests() // 권한요청 처리 설정 메서드
.antMatchers("/h2-console/**").permitAll() // 누구나 h2-console 접속 허용
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/mgmt/**", "POST"))
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
@Override
@Bean
public PortalAuthenticationManager authenticationManager() {
return portalAuthenticationManager;
}
}
@@ -0,0 +1,28 @@
package com.eactive.testmaster.config;
import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ServletConfig {
@Value("${server.port.http}")
private int serverPortHttp;
@Bean
public ServletWebServerFactory serverFactory() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(createStandardConnector());
return tomcat;
}
private Connector createStandardConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(serverPortHttp);
return connector;
}
}
@@ -0,0 +1,97 @@
package com.eactive.testmaster.config;
import com.eactive.testmaster.common.interceptor.AuthenticInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.http.CacheControl;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.resource.PathResourceResolver;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
WebMvcConfigurer.super.addViewControllers(registry);
}
private List<String> listAdminAuthPattern() {
List<String> patterns = new ArrayList<>();
patterns.add("/mgmt/users/*.do");
patterns.add("/mgmt/servers/*.do");
return patterns;
}
@Bean
public AuthenticInterceptor authenticInterceptor() {
AuthenticInterceptor authenticInterceptor = new AuthenticInterceptor();
authenticInterceptor.setAdminAuthPatternList(listAdminAuthPattern());
return authenticInterceptor;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(pageableHandlerMethodArgumentResolver());
}
@Bean
public PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver() {
PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver = new PageableHandlerMethodArgumentResolver();
pageableHandlerMethodArgumentResolver.setOneIndexedParameters(true); //1부터 시작
pageableHandlerMethodArgumentResolver.setFallbackPageable(PageRequest.of(0, 10)); //파라미터 없을 때 기본 페이지
return pageableHandlerMethodArgumentResolver;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticInterceptor())
.addPathPatterns(
"/",
"/mgmt/**/*.do")
.excludePathPatterns(
"/h2-console/**",
"/css/**",
"/html/**",
"/images/**",
"/img/**",
"/js/**",
"/login",
"/intro.do",
"/join.do",
"/user_register.do",
"/actionLogin.do",
"/check_id.do",
"/search_id.do",
"/admin_register_view.do",
"/admin_register.do",
"/request_register_auth.do",
"/reset_password.do",
"/forgot_password.do"
);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**").addResourceLocations("/css/", "classpath:/static/css/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
registry.addResourceHandler("/fonts/**").addResourceLocations("/fonts/", "classpath:/static/fonts/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
registry.addResourceHandler("/font/**").addResourceLocations("/font/", "classpath:/static/fonts/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
registry.addResourceHandler("/html/**").addResourceLocations("/html/", "classpath:/static/html/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
registry.addResourceHandler("/images/**").addResourceLocations("/images/", "classpath:/static/images/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
registry.addResourceHandler("/img/**").addResourceLocations("/img/", "classpath:/static/img/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
registry.addResourceHandler("/js/**").addResourceLocations("/js/", "classpath:/static/js/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/", "classpath:/static/plugins/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
registry.addResourceHandler("/favicon.ico").addResourceLocations("/favicon.ico", "classpath:/static/favicon.ico").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
}
}
@@ -0,0 +1,13 @@
package com.eactive.testmaster.home;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "redirect:/mgmt/routes/list_view.do";
}
}
@@ -0,0 +1,82 @@
package com.eactive.testmaster.login.controller;
import com.eactive.testmaster.user.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
/**
* 일반 로그인을 처리하는 컨트롤러 클래스
*
* @author
* @version 1.0
*/
@Controller
public class LoginController {
public static final String LOGIN_MESSAGE = "loginMessage";
private final UserService userService;
public LoginController(UserService userService) {
this.userService = userService;
}
/**
* 로그인 화면으로 들어간다
*
* @return 로그인 페이지
*/
@GetMapping("/login")
public String loginUsrView(HttpSession session,
ModelMap model) {
if (!userService.isInitialized()) {
return "redirect:/admin_register_view.do";
}
String message = (String) session.getAttribute(LOGIN_MESSAGE);
if (StringUtils.isNotEmpty(message)) {
model.addAttribute(LOGIN_MESSAGE, message);
}
session.setAttribute(LOGIN_MESSAGE, null);
return "page/login";
}
/**
* 세션타임아웃 시간을 연장한다.
*
* @return result - String
*/
@GetMapping(value = "/refreshSessionTimeout.do", produces = "text/html;charset=utf-8")
@ResponseBody
public ResponseEntity<String> refreshSessionTimeout(@RequestParam Map<String, Object> commandMap) {
return ResponseEntity.ok("success");
}
@GetMapping("/actionLogout.do")
public String logout(HttpServletRequest request, HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authentication.getPrincipal().equals("anonymousUser")) {
new SecurityContextLogoutHandler().logout(request, response, authentication);
}
return "redirect:/";
}
}
@@ -0,0 +1,24 @@
package com.eactive.testmaster.login.dto;
public class FindIdRequest {
private String name;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
@@ -0,0 +1,24 @@
package com.eactive.testmaster.login.dto;
public class LoginRequestDTO {
private String id;
private String password;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -0,0 +1,22 @@
package com.eactive.testmaster.login.service;
import com.eactive.testmaster.login.dto.LoginRequestDTO;
/**
* 로그인을 처리하는 비즈니스 인터페이스 클래스
*
* @author
* @version 1.0
*/
public interface LoginService {
/**
* 로그인인증제한을 처리한다.
*
* @param loginRequestDTO LoginRequestDTO
* @return String
* @throws Exception
*/
void processIncorrectLogin(LoginRequestDTO loginRequestDTO);
}
@@ -0,0 +1,42 @@
package com.eactive.testmaster.login.service.impl;
import com.eactive.testmaster.common.entity.EnabledStatus;
import com.eactive.testmaster.login.dto.LoginRequestDTO;
import com.eactive.testmaster.login.service.LoginService;
import com.eactive.testmaster.user.entity.User;
import com.eactive.testmaster.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 로그인을 처리하는 비즈니스 구현 클래스
*
* @author
* @version 1.0
* @see
* @since 2009.03.06
*/
@Service("loginService")
@Transactional
public class LoginServiceImpl implements LoginService {
@Autowired
UserService userService;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processIncorrectLogin(LoginRequestDTO loginRequestDTO) {
User user = userService.findByUserId(loginRequestDTO.getId());
user.setLockCnt(user.getLockCnt() + 1);
if (user.getLockCnt() >= 5) {
user.setLockAt(EnabledStatus.Y);
}
userService.updateUser(user);
}
}
@@ -0,0 +1,121 @@
package com.eactive.testmaster.server.controller;
import com.eactive.testmaster.server.dto.ServerDTO;
import com.eactive.testmaster.server.dto.ServerSearch;
import com.eactive.testmaster.server.entity.Server;
import com.eactive.testmaster.server.mapper.ServerMapper;
import com.eactive.testmaster.server.service.ServerMgmtService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@RequestMapping("/mgmt/servers")
public class ServerMgmtController {
private final ServerMgmtService serverMgmtService;
private final ServerMapper serverMapper;
private final Validator validator;
public ServerMgmtController(ServerMgmtService serverMgmtService, ServerMapper serverMapper, Validator validator) {
this.serverMgmtService = serverMgmtService;
this.serverMapper = serverMapper;
this.validator = validator;
}
public static final String SERVER = "server";
public static final String SERVER_EDIT = "page/servers/serverEdit";
public static final String SERVER_LIST_VIEW = "redirect:/mgmt/servers/list_view.do";
@GetMapping("/list_view.do")
public String listView(@ModelAttribute("serverSearch") ServerSearch serverSearch,
ModelMap model,
Pageable pageable) {
Page<Server> page = serverMgmtService.findAll(serverSearch.buildSpecification(), pageable);
model.addAttribute("page", page);
model.addAttribute("pageable", pageable);
model.addAttribute("modelSearch", serverSearch);
return "page/servers/serverList";
}
@GetMapping("/create_view.do")
public String createView(ModelMap model) {
ServerDTO defaultServer = new ServerDTO();
model.addAttribute(SERVER, defaultServer);
return SERVER_EDIT;
}
@GetMapping("/update_view.do")
public String updateView(ModelMap model, @RequestParam("id") Long id) {
Server found = serverMgmtService.findById(id);
ServerDTO server = serverMapper.map(found);
model.addAttribute(SERVER, server);
return SERVER_EDIT;
}
@PostMapping("/register.do")
public String register(@ModelAttribute(SERVER) ServerDTO dto,
BindingResult bindingResult,
ModelMap model) {
if (dto == null) {
return "redirect:/mgmt/servers/create_view.do";
}
validator.validate(dto, bindingResult);
if (bindingResult.hasErrors()) {
model.addAttribute(SERVER, dto);
return SERVER_EDIT;
}
Server server = serverMapper.map(dto);
serverMgmtService.create(server);
return SERVER_LIST_VIEW;
}
@PostMapping("/update.do")
public String update(@ModelAttribute(SERVER) ServerDTO dto,
BindingResult bindingResult,
ModelMap model) {
if (StringUtils.isEmpty(dto.getId().toString())) {
return SERVER_LIST_VIEW;
}
validator.validate(dto, bindingResult);
if (bindingResult.hasErrors()) {
model.addAttribute(SERVER, dto);
return SERVER_EDIT;
}
Server updated = serverMapper.map(dto);
serverMgmtService.update(dto.getId(), updated);
return SERVER_LIST_VIEW;
}
@PostMapping("/delete.do")
public String delete(@RequestParam("checkedIdForDel") List<String> checkedIdForDel) {
for (String id : checkedIdForDel) {
serverMgmtService.delete(Long.parseLong(id));
}
return SERVER_LIST_VIEW;
}
}
@@ -0,0 +1,80 @@
package com.eactive.testmaster.server.dto;
import javax.validation.constraints.NotEmpty;
public class ServerDTO {
private Long id;
@NotEmpty
private String name;
@NotEmpty
private String scheme = "http";
@NotEmpty
private String hostname;
@NotEmpty
private String basePath = "/";
private Integer port = 80;
private boolean enabled = true;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getScheme() {
return scheme;
}
public void setScheme(String scheme) {
this.scheme = scheme;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getBasePath() {
return basePath;
}
public void setBasePath(String basePath) {
this.basePath = basePath;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
@@ -0,0 +1,78 @@
package com.eactive.testmaster.server.dto;
import com.eactive.testmaster.common.search.BaseSearch;
import com.eactive.testmaster.common.search.ColumnSearchModel;
import com.eactive.testmaster.common.search.SearchCondition;
import com.eactive.testmaster.common.search.SearchModel;
import com.eactive.testmaster.server.entity.Server;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class ServerSearch implements BaseSearch<Server> {
private String name;
private String hostname;
private String basePath;
private String port;
@Override
public List<SearchModel> buildSearchCondition() {
List<SearchModel> models = new ArrayList<>();
if (StringUtils.isNotEmpty(name)) {
models.add(new ColumnSearchModel("name", SearchCondition.LIKE, name));
}
if (StringUtils.isNotEmpty(hostname)) {
models.add(new ColumnSearchModel("hostname", SearchCondition.LIKE, hostname));
}
if (StringUtils.isNotEmpty(basePath)) {
models.add(new ColumnSearchModel("basePath", SearchCondition.LIKE, basePath));
}
if (StringUtils.isNotEmpty(port)) {
models.add(new ColumnSearchModel("port", SearchCondition.EQUAL, Integer.parseInt(port)));
}
return models;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getBasePath() {
return basePath;
}
public void setBasePath(String basePath) {
this.basePath = basePath;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
}
@@ -0,0 +1,88 @@
package com.eactive.testmaster.server.entity;
import javax.persistence.*;
@Entity
@Table(name = "SERVER")
public class Server {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
private String scheme;
private String hostname;
private String basePath;
private Integer port;
@Column(name = "IS_ENABLED", columnDefinition = "boolean default true")
private boolean enabled;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getScheme() {
return scheme;
}
public void setScheme(String scheme) {
this.scheme = scheme;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getBasePath() {
return basePath;
}
public void setBasePath(String basePath) {
this.basePath = basePath;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Transient
public String getFullHostAddress() {
return scheme + "://" + hostname + (port == null ? "" : ":" + port) + basePath;
}
}
@@ -0,0 +1,13 @@
package com.eactive.testmaster.server.entity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
public interface ServerRepository extends JpaRepository<Server, Long>, JpaSpecificationExecutor<Server> {
List<Server> findAllByEnabledIs(boolean enabled);
}
@@ -0,0 +1,62 @@
package com.eactive.testmaster.server.mapper;
import com.eactive.testmaster.common.exception.NotFoundException;
import com.eactive.testmaster.server.dto.ServerDTO;
import com.eactive.testmaster.server.entity.Server;
import com.eactive.testmaster.server.entity.ServerRepository;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public class ServerMapper {
@Autowired
ServerRepository serverRepository;
public Server mapById(Long id) {
if (id == null || id == 0) return null;
return serverRepository.findById(id).orElseThrow(() -> new NotFoundException("Server[" + id + "] not found"));
}
public Long mapById(Server server) {
if (server == null) return null;
return server.getId();
}
public ServerDTO map(Server server) {
if (server == null) return null;
ServerDTO dto = new ServerDTO();
dto.setId(server.getId());
dto.setName(server.getName());
dto.setHostname(server.getHostname());
dto.setPort(server.getPort());
dto.setScheme(server.getScheme());
dto.setBasePath(server.getBasePath());
dto.setEnabled(server.isEnabled());
return dto;
}
public Server map(ServerDTO dto) {
if (dto == null) return null;
Server server = new Server();
server.setId(dto.getId());
server.setName(dto.getName());
server.setHostname(dto.getHostname());
server.setPort(dto.getPort());
server.setScheme(dto.getScheme());
server.setBasePath(dto.getBasePath());
server.setEnabled(dto.isEnabled());
return server;
}
}
@@ -0,0 +1,51 @@
package com.eactive.testmaster.server.service;
import com.eactive.testmaster.common.exception.NotFoundException;
import com.eactive.testmaster.server.entity.Server;
import com.eactive.testmaster.server.entity.ServerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@Service
@Transactional
public class ServerMgmtService {
@Autowired
private ServerRepository serverRepository;
public Page<Server> findAll(Specification<Server> serverSpecification, Pageable pageable) {
return serverRepository.findAll(serverSpecification, pageable);
}
public Server findById(Long id) {
return serverRepository.findById(id).orElseThrow(() -> new NotFoundException("Server Not Found"));
}
public void create(Server server) {
serverRepository.save(server);
}
public void update(Long id, Server updated) {
Server server = serverRepository.findById(id).orElseThrow(() -> new NotFoundException("Server Not Found"));
server.setName(updated.getName());
server.setScheme(updated.getScheme());
server.setHostname(updated.getHostname());
server.setBasePath(updated.getBasePath());
server.setPort(updated.getPort());
server.setEnabled(updated.isEnabled());
serverRepository.save(server);
}
public void delete(Long id) {
serverRepository.deleteById(id);
}
}
@@ -0,0 +1,57 @@
package com.eactive.testmaster.user.controller;
import com.eactive.testmaster.common.util.SecurityUtil;
import com.eactive.testmaster.user.dto.PasswordChangeRequestDTO;
import com.eactive.testmaster.user.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class AccountController {
public static final String UPDATE_PW = "page/updatePw";
private Validator validator;
private UserService userService;
public AccountController(Validator validator, UserService userService) {
this.validator = validator;
this.userService = userService;
}
@GetMapping("/change_password.do")
public String changePassword(@ModelAttribute("passwordChangeRequest") PasswordChangeRequestDTO passwordChangeRequestDTO) {
return UPDATE_PW;
}
@PostMapping(value = "/update_password.do")
@Transactional
public String updatePassword(ModelMap model, @ModelAttribute("passwordChangeRequest") PasswordChangeRequestDTO passwordChangeRequestDTO,
BindingResult result) {
validator.validate(passwordChangeRequestDTO, result);
if (result.hasErrors()) {
model.addAttribute("errors", result);
} else {
String oldPassword = passwordChangeRequestDTO.getOldPassword();
String newPassword = passwordChangeRequestDTO.getNewPassword();
String newPassword2 = passwordChangeRequestDTO.getNewPassword2();
String resultMsg = userService.updateUserPassword(SecurityUtil.getCurrentUserEsntlId(), oldPassword, newPassword, newPassword2);
model.addAttribute("resultMsg", resultMsg);
model.addAttribute("passwordChangeRequest", new PasswordChangeRequestDTO());
}
return UPDATE_PW;
}
}
@@ -0,0 +1,68 @@
package com.eactive.testmaster.user.controller;
import com.eactive.testmaster.user.dto.UserRegisterDTO;
import com.eactive.testmaster.user.entity.StaffUser;
import com.eactive.testmaster.user.mapper.StaffUserMapper;
import com.eactive.testmaster.user.repository.StaffUserRepository;
import com.eactive.testmaster.user.service.StaffUserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class AdminRegisterController {
private final StaffUserService staffUserService;
private final StaffUserRepository staffUserRepository;
private final Validator validator;
private final StaffUserMapper staffUserMapper;
public AdminRegisterController(StaffUserService staffUserService, StaffUserRepository staffUserRepository, Validator validator, StaffUserMapper staffUserMapper) {
this.staffUserService = staffUserService;
this.staffUserRepository = staffUserRepository;
this.validator = validator;
this.staffUserMapper = staffUserMapper;
}
@GetMapping("/admin_register_view.do")
public String adminRegisterView(ModelMap model) {
long users = staffUserRepository.count();
if (users == 0) {
model.addAttribute("user", new StaffUser());
return "page/users/adminRegister";
} else {
return "redirect:/";
}
}
@PostMapping("/admin_register.do")
public String adminRegister(@ModelAttribute("user") UserRegisterDTO user,
BindingResult bindingResult,
ModelMap model) {
if (user == null) {
return "redirect:/admin_register_view.do";
}
validator.validate(user, bindingResult);
if (bindingResult.hasErrors()) {
model.addAttribute("user", user);
return "page/users/adminRegister";
}
StaffUser newUser = staffUserMapper.map(user);
newUser.setUserSecurity("ROLE_ADMIN");
staffUserService.create(newUser);
return "redirect:/login";
}
}

Some files were not shown because too many files have changed in this diff Show More