스크립트 기능 추가

This commit is contained in:
yunjy
2022-08-19 10:37:36 +09:00
parent f5fb0e39ee
commit 83568bd9d5
15 changed files with 790 additions and 2 deletions
@@ -1,5 +1,6 @@
package com.eactive.httpmockserver;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
@@ -7,6 +8,8 @@ import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.eactive.httpmockserver.script.Script;
import com.eactive.httpmockserver.script.ScriptLoader;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
@@ -21,6 +24,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.server.ResponseStatusException;
@@ -41,7 +45,7 @@ public class ApiController {
//@RequestMapping(value = "/mock-api/**")
@RequestMapping(value = "/{_:^(?!plugins|dist|favicon.ico).*$}/**")
public ResponseEntity<String> mockApi(HttpEntity<String> httpEntity, HttpServletRequest request,
HttpServletResponse response) {
HttpServletResponse response) throws Exception {
logger.info("==================================================");
logger.info("==================================================");
logger.info(request.getServletPath());
@@ -119,6 +123,14 @@ public class ApiController {
}
String responseStr = assignResponseBody(api);
String scriptName = api.getScriptName();
if(StringUtils.isNotBlank(scriptName)) {
Script script = ScriptLoader.getInstance().newScript(scriptName);
logger.info("Execute postProcess Script={}", scriptName);
responseStr = script.postProcess(httpEntity.getBody(), responseStr);
}
logger.info("ResponseBody={}", responseStr);
return new ResponseEntity<String>(responseStr, responseHeaders, responseStatusCode);
@@ -43,6 +43,9 @@ public class ApiInfo implements Serializable {
@Column(length = 10)
private String method;
@Column(length = 500)
private String scriptName;
@Column(length = 50)
private String responseContentType;
@@ -181,6 +184,14 @@ public class ApiInfo implements Serializable {
this.syntaxResponseBody2 = syntaxResponseBody2;
}
public String getScriptName() {
return scriptName;
}
public void setScriptName(String scriptName) {
this.scriptName = scriptName;
}
@Override
public String toString() {
return "ApiInfo{" + "id=" + id + ", apiGroupName='" + apiGroupName + '\'' + ", apiName='" + apiName + '\''
@@ -0,0 +1,6 @@
package com.eactive.httpmockserver.script;
public interface Script {
public String postProcess(String requestMessage, String responseMessage) throws Exception;
}
@@ -0,0 +1,118 @@
package com.eactive.httpmockserver.script;
import com.eactive.httpmockserver.ApiManageRestController;
import com.eactive.httpmockserver.datatables.DataTablesRequest;
import com.eactive.httpmockserver.datatables.DataTablesResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import javax.validation.Valid;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Optional;
@Controller
public class ScriptController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ScriptInfoService scriptInfoService;
@GetMapping("/manage/findAllScriptInfos")
public String getAllScriptInfoForDatatables(Pageable pageable, Model model) {
return "page/list-script-info";
}
@GetMapping(path = { "/manage/getScriptInfoForm", "/manage/getScriptInfoForm/{id}" })
public String getScriptInfoForm(Model model, @PathVariable Optional<Long> id) throws IOException {
if (id.isPresent()) {
Optional<ScriptInfo> optional = scriptInfoService.findById(id.get());
if (optional.isPresent()) {
model.addAttribute("scriptInfo", optional.get());
} else {
throw new IllegalArgumentException("No data, id: " + id.get());
}
} else {
ScriptInfo spec = new ScriptInfo();
InputStream is = getClass().getClassLoader().getResourceAsStream("basecodes/BaseScript.java.template");
String baseCode = IOUtils.toString(is, "utf-8");
spec.setScriptCode(baseCode);
model.addAttribute("scriptInfo", spec);
}
return "page/add-edit-script-info";
}
@PostMapping("/manage/saveScriptInfo")
public String saveScriptInfo(@Valid ScriptInfo spec, BindingResult result, Model model) throws IOException, ClassNotFoundException {
if (result.hasErrors()) {
return "page/add-edit-script-info";
}
ScriptLoader.getInstance().load(spec.getScriptName(), spec.getScriptCode());
if (spec.getId() == null) {
scriptInfoService.save(spec);
} else {
scriptInfoService.update(spec);
}
if (BooleanUtils.toBoolean(spec.getUseYn())) {
// 기존설정 useYn=N으로 수정
scriptInfoService.setUnuseOthers(spec.getId());
}
return "redirect:/manage/findAllScriptInfos";
}
@RequestMapping("/manage/deleteScriptInfo/{id}")
public String deleteScriptInfo(@PathVariable Long id) throws IOException, ClassNotFoundException {
if (id == null) {
throw new IllegalArgumentException("No data, id: " + id);
}
String scriptName = scriptInfoService.findById(id).get().getScriptName();
ScriptLoader.getInstance().unLoad(scriptName);
scriptInfoService.delete(id);
return "redirect:/manage/findAllScriptInfos";
}
@PostMapping("/manage/rest/scriptInfos")
@ResponseBody
public DataTablesResponse<ScriptInfo> getAllScriptInfoForDatatables(@RequestBody DataTablesRequest req) {
Pageable pageable = ApiManageRestController.assignJpaPageable(req);
// search option
Example<ScriptInfo> example = null;
if (StringUtils.isNotBlank(req.getSearch().getValue())) {
ScriptInfo spec = new ScriptInfo();
spec.setScriptName(req.getSearch().getValue());
ExampleMatcher matcher = ExampleMatcher.matchingAny().withMatcher("specName",
new ExampleMatcher.GenericPropertyMatcher().contains().ignoreCase());
example = Example.of(spec, matcher);
}
DataTablesResponse<ScriptInfo> res = new DataTablesResponse<ScriptInfo>(
scriptInfoService.findAllScriptInfos(example, pageable));
res.setRecordsFiltered(scriptInfoService.getRecordsCount(example));
res.setRecordsTotal(scriptInfoService.getRecordsCount(null));
res.setDraw(req.getDraw());
return res;
}
}
@@ -0,0 +1,67 @@
package com.eactive.httpmockserver.script;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@Entity
public class ScriptInfo implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Column(length = 500, nullable = false)
private String scriptName;
@NotBlank
@Column(length = 4000, nullable = false)
private String scriptDesc;
@Column
@Lob
private String scriptCode;
@Column(length = 1)
private String useYn;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getScriptName() {
return scriptName;
}
public void setScriptName(String scriptName) {
this.scriptName = scriptName;
}
public String getScriptDesc() {
return scriptDesc;
}
public void setScriptDesc(String scriptDesc) {
this.scriptDesc = scriptDesc;
}
public String getScriptCode() {
return scriptCode;
}
public void setScriptCode(String scriptCode) {
this.scriptCode = scriptCode;
}
public String getUseYn() {
return useYn;
}
public void setUseYn(String useYn) {
this.useYn = useYn;
}
}
@@ -0,0 +1,16 @@
package com.eactive.httpmockserver.script;
import com.eactive.httpmockserver.socket.BytesMessageSpec;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
@Repository
public interface ScriptInfoDao extends JpaRepository<ScriptInfo, Long> {
ScriptInfo findFirstByUseYn(String useYn);
@Modifying
@Query(value = "update ScriptInfo b set b.useYn='N' where b.id <> ?1")
void updateUnuseOthers(Long id);
}
@@ -0,0 +1,27 @@
package com.eactive.httpmockserver.script;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Pageable;
import java.util.List;
import java.util.Optional;
public interface ScriptInfoService {
public ScriptInfo findActiveScriptInfo();
public List<ScriptInfo> findAllScriptInfos(Pageable pageable);
public List<ScriptInfo> findAllScriptInfos(Example<ScriptInfo> example, Pageable pageable);
public long getRecordsCount(Example<ScriptInfo> example);
public Optional<ScriptInfo> findById(Long id);
public ScriptInfo save(ScriptInfo spec);
public ScriptInfo update(ScriptInfo spec);
public void delete(Long id);
public void setUnuseOthers(Long id);
}
@@ -0,0 +1,87 @@
package com.eactive.httpmockserver.script;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
@Transactional(propagation = Propagation.SUPPORTS)
public class ScriptInfoServiceImpl implements ScriptInfoService {
@Autowired
private ScriptInfoDao dao;
@Override
public ScriptInfo findActiveScriptInfo() {
return dao.findFirstByUseYn("Y");
}
@Override
public List<ScriptInfo> findAllScriptInfos(Pageable pageable) {
Page<ScriptInfo> pagedResult = dao.findAll(pageable);
if (pagedResult.hasContent()) {
return pagedResult.getContent();
} else {
return new ArrayList<ScriptInfo>();
}
}
@Override
public List<ScriptInfo> findAllScriptInfos(Example<ScriptInfo> example, Pageable pageable) {
if (example == null) {
return findAllScriptInfos(pageable);
}
Page<ScriptInfo> pagedResult = dao.findAll(example, pageable);
if (pagedResult.hasContent()) {
return pagedResult.getContent();
} else {
return new ArrayList<ScriptInfo>();
}
}
@Override
public Optional<ScriptInfo> findById(Long id) {
return dao.findById(id);
}
@Override
public long getRecordsCount(Example<ScriptInfo> example) {
if (example == null) {
return dao.count();
} else {
return dao.count(example);
}
}
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public ScriptInfo save(ScriptInfo spec) {
return dao.save(spec);
}
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public ScriptInfo update(ScriptInfo spec) {
return dao.save(spec);
}
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void delete(Long id) {
dao.deleteById(id);
}
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void setUnuseOthers(Long id) {
dao.updateUnuseOthers(id);
}
}
@@ -0,0 +1,82 @@
package com.eactive.httpmockserver.script;
import org.apache.commons.io.IOUtils;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
public class ScriptLoader{
String path = ScriptLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
public static String DYNAMIC_BASE_PACKAGE = "com.eactive.httpmockserver.script.dynamic";
public static String DYNAMIC_BASE_PATH = DYNAMIC_BASE_PACKAGE.replaceAll("[.]","/");
private static final ScriptLoader instance = new ScriptLoader();
private Map<String, Class<Script>> loadedClass;
private ScriptLoader(){
this.loadedClass = new HashMap<>();
}
public static ScriptLoader getInstance(){
return instance;
}
public Script newScript(String scriptName) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Script> scriptClass = this.loadedClass.get(scriptName);
Script newInstance = scriptClass.getDeclaredConstructor().newInstance();
return newInstance;
}
class DynamicLoader extends ClassLoader{
public Class<Script> loadScript(String fullClassName, byte[] classData){
return (Class<Script>) defineClass(fullClassName,
classData, 0, classData.length);
}
}
public void load(String scriptName, String scriptCode) throws ClassNotFoundException, IOException {
String source = scriptCode;
String fullPath = DYNAMIC_BASE_PATH + "/" + scriptName;
String fullPackage = DYNAMIC_BASE_PACKAGE + "." + scriptName;
File packagePath = new File(path + DYNAMIC_BASE_PATH);
if(!packagePath.exists())
packagePath.mkdirs();
File sourceFile = new File(path + fullPath + ".java");
new FileWriter(sourceFile).append(source).close();
// 만들어진 Java 파일을 컴파일한다.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, System.out, System.out, sourceFile.getPath());
// File classPath = new File(path + DYNAMIC_BASE_PATH);
// 컴파일된 Class를 Load
// URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] {classPath.toURI().toURL()});
// Class<Script> cls = (Class<Script>) Class.forName(fullPackage, true, classLoader);
// new File(path + DYNAMIC_BASE_PATH + "/" + scriptName +".class").delete();
// sourceFile.delete();
File classFile = new File(path + DYNAMIC_BASE_PATH + "/" + scriptName +".class");
byte[] classData = IOUtils.toByteArray(new FileInputStream(classFile));
DynamicLoader dynamicLoader = new DynamicLoader();
Class<Script> cls = dynamicLoader.loadScript(fullPackage, classData);
this.loadedClass.put(scriptName, cls);
}
public void unLoad(String scriptName) throws ClassNotFoundException, IOException {
new File(path + DYNAMIC_BASE_PATH + "/" + scriptName +".class").delete();
this.loadedClass.remove(scriptName);
}
}