From b9f58a891094ea1c0fc1f3c7c80ad38256387ec9 Mon Sep 17 00:00:00 2001 From: haifeng414 Date: Wed, 19 Feb 2020 20:25:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=A7=A3=E6=9E=90jmx?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...{LoadTestFileType.java => EngineType.java} | 2 +- .../io/metersphere/engine/EngineContext.java | 20 +++ .../io/metersphere/engine/EngineFactory.java | 35 +++++- .../metersphere/parse/EngineSourceParser.java | 9 ++ .../parse/EngineSourceParserFactory.java | 16 +++ .../parse/xml/XmlEngineSourceParse.java | 32 +++++ .../parse/xml/reader/DocumentParser.java | 10 ++ .../xml/reader/DocumentParserFactory.java | 16 +++ .../xml/reader/jmx/JmeterDocumentParser.java | 116 +++++++++++++++++ .../metersphere/service/LoadTestService.java | 11 +- .../java/io/metersphere/JmxFileParseTest.java | 119 ++++++++++++++++++ 11 files changed, 378 insertions(+), 8 deletions(-) rename backend/src/main/java/io/metersphere/commons/constants/{LoadTestFileType.java => EngineType.java} (63%) create mode 100644 backend/src/main/java/io/metersphere/parse/EngineSourceParser.java create mode 100644 backend/src/main/java/io/metersphere/parse/EngineSourceParserFactory.java create mode 100644 backend/src/main/java/io/metersphere/parse/xml/XmlEngineSourceParse.java create mode 100644 backend/src/main/java/io/metersphere/parse/xml/reader/DocumentParser.java create mode 100644 backend/src/main/java/io/metersphere/parse/xml/reader/DocumentParserFactory.java create mode 100644 backend/src/main/java/io/metersphere/parse/xml/reader/jmx/JmeterDocumentParser.java create mode 100644 backend/src/test/java/io/metersphere/JmxFileParseTest.java diff --git a/backend/src/main/java/io/metersphere/commons/constants/LoadTestFileType.java b/backend/src/main/java/io/metersphere/commons/constants/EngineType.java similarity index 63% rename from backend/src/main/java/io/metersphere/commons/constants/LoadTestFileType.java rename to backend/src/main/java/io/metersphere/commons/constants/EngineType.java index ffdce75b98..5277f90647 100644 --- a/backend/src/main/java/io/metersphere/commons/constants/LoadTestFileType.java +++ b/backend/src/main/java/io/metersphere/commons/constants/EngineType.java @@ -1,5 +1,5 @@ package io.metersphere.commons.constants; -public enum LoadTestFileType { +public enum EngineType { JMX } diff --git a/backend/src/main/java/io/metersphere/engine/EngineContext.java b/backend/src/main/java/io/metersphere/engine/EngineContext.java index 94711a1973..a6eaf79851 100644 --- a/backend/src/main/java/io/metersphere/engine/EngineContext.java +++ b/backend/src/main/java/io/metersphere/engine/EngineContext.java @@ -1,10 +1,14 @@ package io.metersphere.engine; import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; public class EngineContext { private String engineId; + private String engineType; private InputStream inputStream; + private Map properties = new HashMap<>(); public String getEngineId() { return engineId; @@ -14,6 +18,14 @@ public class EngineContext { this.engineId = engineId; } + public String getEngineType() { + return engineType; + } + + public void setEngineType(String engineType) { + this.engineType = engineType; + } + public InputStream getInputStream() { return inputStream; } @@ -21,4 +33,12 @@ public class EngineContext { public void setInputStream(InputStream inputStream) { this.inputStream = inputStream; } + + public void addProperty(String key, Object value) { + this.properties.put(key, value); + } + + public Object getProperty(String key) { + return this.properties.get(key); + } } diff --git a/backend/src/main/java/io/metersphere/engine/EngineFactory.java b/backend/src/main/java/io/metersphere/engine/EngineFactory.java index b7a6c81e96..c17671f29e 100644 --- a/backend/src/main/java/io/metersphere/engine/EngineFactory.java +++ b/backend/src/main/java/io/metersphere/engine/EngineFactory.java @@ -1,27 +1,54 @@ package io.metersphere.engine; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.FileContent; +import io.metersphere.base.domain.FileMetadata; import io.metersphere.base.domain.LoadTestWithBLOBs; -import io.metersphere.commons.constants.LoadTestFileType; +import io.metersphere.commons.constants.EngineType; +import io.metersphere.commons.exception.MSException; import io.metersphere.engine.jmx.JmxEngine; +import io.metersphere.parse.EngineSourceParser; +import io.metersphere.parse.EngineSourceParserFactory; +import org.apache.commons.lang3.StringUtils; import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.nio.charset.StandardCharsets; public class EngineFactory { public static Engine createEngine(String engineType) { - final LoadTestFileType type = LoadTestFileType.valueOf(engineType); + final EngineType type = EngineType.valueOf(engineType); - if (type == LoadTestFileType.JMX) { + if (type == EngineType.JMX) { return new JmxEngine(); } return null; } - public static EngineContext createContext(LoadTestWithBLOBs loadTest, FileContent fileContent) { + public static EngineContext createContext(LoadTestWithBLOBs loadTest, FileMetadata fileMetadata, FileContent fileContent) throws Exception { final EngineContext engineContext = new EngineContext(); engineContext.setEngineId(loadTest.getId()); engineContext.setInputStream(new ByteArrayInputStream(fileContent.getFile().getBytes(StandardCharsets.UTF_8))); + engineContext.setEngineType(fileMetadata.getType()); + + if (!StringUtils.isEmpty(loadTest.getLoadConfiguration())) { + final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration()); + + for (int i = 0; i < jsonArray.size(); i++) { + final JSONObject jsonObject = jsonArray.getJSONObject(i); + engineContext.addProperty(jsonObject.getString("key"), jsonObject.get("value")); + } + } + + final EngineSourceParser engineSourceParser = EngineSourceParserFactory.createEngineSourceParser(engineContext.getEngineType()); + + if (engineSourceParser == null) { + MSException.throwException("未知的文件类型!"); + } + + final InputStream inputStream = engineSourceParser.parse(engineContext, engineContext.getInputStream()); + engineContext.setInputStream(inputStream); return engineContext; } diff --git a/backend/src/main/java/io/metersphere/parse/EngineSourceParser.java b/backend/src/main/java/io/metersphere/parse/EngineSourceParser.java new file mode 100644 index 0000000000..ef3a8d8c0e --- /dev/null +++ b/backend/src/main/java/io/metersphere/parse/EngineSourceParser.java @@ -0,0 +1,9 @@ +package io.metersphere.parse; + +import io.metersphere.engine.EngineContext; + +import java.io.InputStream; + +public interface EngineSourceParser { + InputStream parse(EngineContext context, InputStream source) throws Exception; +} diff --git a/backend/src/main/java/io/metersphere/parse/EngineSourceParserFactory.java b/backend/src/main/java/io/metersphere/parse/EngineSourceParserFactory.java new file mode 100644 index 0000000000..5fd673fdbc --- /dev/null +++ b/backend/src/main/java/io/metersphere/parse/EngineSourceParserFactory.java @@ -0,0 +1,16 @@ +package io.metersphere.parse; + +import io.metersphere.commons.constants.EngineType; +import io.metersphere.parse.xml.XmlEngineSourceParse; + +public class EngineSourceParserFactory { + public static EngineSourceParser createEngineSourceParser(String type) { + final EngineType engineType = EngineType.valueOf(type); + + if (EngineType.JMX.equals(engineType)) { + return new XmlEngineSourceParse(); + } + + return null; + } +} diff --git a/backend/src/main/java/io/metersphere/parse/xml/XmlEngineSourceParse.java b/backend/src/main/java/io/metersphere/parse/xml/XmlEngineSourceParse.java new file mode 100644 index 0000000000..42648601bb --- /dev/null +++ b/backend/src/main/java/io/metersphere/parse/xml/XmlEngineSourceParse.java @@ -0,0 +1,32 @@ +package io.metersphere.parse.xml; + +import io.metersphere.engine.EngineContext; +import io.metersphere.parse.EngineSourceParser; +import io.metersphere.parse.xml.reader.DocumentParser; +import io.metersphere.parse.xml.reader.DocumentParserFactory; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.InputStream; + +public class XmlEngineSourceParse implements EngineSourceParser { + @Override + public InputStream parse(EngineContext context, InputStream source) throws Exception { + final InputSource inputSource = new InputSource(source); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + final Document document = docBuilder.parse(inputSource); + + final DocumentParser documentParser = createDocumentParser(context.getEngineType()); + + return documentParser.parse(context, document); + } + + private DocumentParser createDocumentParser(String type) { + return DocumentParserFactory.createDocumentParser(type); + } +} diff --git a/backend/src/main/java/io/metersphere/parse/xml/reader/DocumentParser.java b/backend/src/main/java/io/metersphere/parse/xml/reader/DocumentParser.java new file mode 100644 index 0000000000..3e4813760d --- /dev/null +++ b/backend/src/main/java/io/metersphere/parse/xml/reader/DocumentParser.java @@ -0,0 +1,10 @@ +package io.metersphere.parse.xml.reader; + +import io.metersphere.engine.EngineContext; +import org.w3c.dom.Document; + +import java.io.InputStream; + +public interface DocumentParser { + InputStream parse(EngineContext context, Document document) throws Exception; +} diff --git a/backend/src/main/java/io/metersphere/parse/xml/reader/DocumentParserFactory.java b/backend/src/main/java/io/metersphere/parse/xml/reader/DocumentParserFactory.java new file mode 100644 index 0000000000..218499951b --- /dev/null +++ b/backend/src/main/java/io/metersphere/parse/xml/reader/DocumentParserFactory.java @@ -0,0 +1,16 @@ +package io.metersphere.parse.xml.reader; + +import io.metersphere.commons.constants.EngineType; +import io.metersphere.parse.xml.reader.jmx.JmeterDocumentParser; + +public class DocumentParserFactory { + public static DocumentParser createDocumentParser(String type) { + final EngineType engineType = EngineType.valueOf(type); + + if (EngineType.JMX.equals(engineType)) { + return new JmeterDocumentParser(); + } + + return null; + } +} diff --git a/backend/src/main/java/io/metersphere/parse/xml/reader/jmx/JmeterDocumentParser.java b/backend/src/main/java/io/metersphere/parse/xml/reader/jmx/JmeterDocumentParser.java new file mode 100644 index 0000000000..fcba367218 --- /dev/null +++ b/backend/src/main/java/io/metersphere/parse/xml/reader/jmx/JmeterDocumentParser.java @@ -0,0 +1,116 @@ +package io.metersphere.parse.xml.reader.jmx; + +import io.metersphere.engine.EngineContext; +import io.metersphere.parse.xml.reader.DocumentParser; +import org.junit.platform.commons.util.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +public class JmeterDocumentParser implements DocumentParser { + private final static String HASH_TREE_ELEMENT = "hashTree"; + private final static String STRING_PROP = "stringProp"; + private final static String CONCURRENCY_THREAD_GROUP = "com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup"; + private EngineContext context; + + @Override + public InputStream parse(EngineContext context, Document document) throws Exception { + this.context = context; + + final Element jmeterTestPlan = document.getDocumentElement(); + + NodeList childNodes = jmeterTestPlan.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + + if (node instanceof Element) { + Element ele = (Element) node; + + // jmeterTestPlan的子元素肯定是 + parseHashTree(ele); + } + } + + return documentToInputStream(document); + } + + private InputStream documentToInputStream(Document document) throws TransformerException { + DOMSource domSource = new DOMSource(document); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.transform(domSource, result); + final String resultStr = writer.toString(); + return new ByteArrayInputStream(resultStr.getBytes(StandardCharsets.UTF_8)); + } + + private void parseHashTree(Element hashTree) { + if (invalid(hashTree)) { + return; + } + + if (hashTree.getChildNodes().getLength() > 0) { + final NodeList childNodes = hashTree.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element) { + Element ele = (Element) node; + if (invalid(ele)) { + continue; + } + + if (nodeNameEquals(ele, HASH_TREE_ELEMENT)) { + parseHashTree(ele); + } else if (nodeNameEquals(ele, CONCURRENCY_THREAD_GROUP)) { + processConcurrencyThreadGroup(ele); + } + } + } + } + } + + private void processConcurrencyThreadGroup(Element concurrencyThreadGroup) { + if (concurrencyThreadGroup.getChildNodes().getLength() > 0) { + final NodeList childNodes = concurrencyThreadGroup.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element) { + Element ele = (Element) node; + if (invalid(ele)) { + continue; + } + + if (nodeNameEquals(ele, STRING_PROP)) { + parseStringProp(ele); + } + } + } + } + } + + private void parseStringProp(Element stringProp) { + if (stringProp.getChildNodes().getLength() > 0 && context.getProperty(stringProp.getAttribute("name")) != null) { + stringProp.getFirstChild().setNodeValue(context.getProperty(stringProp.getAttribute("name")).toString()); + } + } + + private boolean nodeNameEquals(Node node, String desiredName) { + return desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()); + } + + private boolean invalid(Element ele) { + return !StringUtils.isBlank(ele.getAttribute("enabled")) && !Boolean.parseBoolean(ele.getAttribute("enabled")); + } +} diff --git a/backend/src/main/java/io/metersphere/service/LoadTestService.java b/backend/src/main/java/io/metersphere/service/LoadTestService.java index 6c77ce116a..8b60da2750 100644 --- a/backend/src/main/java/io/metersphere/service/LoadTestService.java +++ b/backend/src/main/java/io/metersphere/service/LoadTestService.java @@ -3,7 +3,7 @@ package io.metersphere.service; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.ExtLoadTestMapper; -import io.metersphere.commons.constants.LoadTestFileType; +import io.metersphere.commons.constants.EngineType; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.IOUtils; import io.metersphere.controller.request.testplan.*; @@ -108,7 +108,7 @@ public class LoadTestService { fileMetadata.setSize(file.getSize()); fileMetadata.setCreateTime(System.currentTimeMillis()); fileMetadata.setUpdateTime(System.currentTimeMillis()); - fileMetadata.setType(LoadTestFileType.JMX.name()); + fileMetadata.setType(EngineType.JMX.name()); fileMetadataMapper.insert(fileMetadata); FileContent fileContent = new FileContent(); @@ -171,7 +171,12 @@ public class LoadTestService { fileMetadata.getType())); } - final boolean init = engine.init(EngineFactory.createContext(loadTest, fileContent)); + boolean init = true; + try { + init = engine.init(EngineFactory.createContext(loadTest, fileMetadata, fileContent)); + } catch (Exception e) { + MSException.throwException(e); + } if (!init) { MSException.throwException(String.format("无法运行测试,初始化运行环境失败,测试ID:%s", request.getId())); } diff --git a/backend/src/test/java/io/metersphere/JmxFileParseTest.java b/backend/src/test/java/io/metersphere/JmxFileParseTest.java new file mode 100644 index 0000000000..9d582f07c6 --- /dev/null +++ b/backend/src/test/java/io/metersphere/JmxFileParseTest.java @@ -0,0 +1,119 @@ +package io.metersphere; + +import org.junit.Test; +import org.junit.platform.commons.util.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.File; +import java.io.FileInputStream; +import java.io.StringWriter; + +public class JmxFileParseTest { + private final static String HASH_TREE_ELEMENT = "hashTree"; + private final static String STRING_PROP = "stringProp"; + private final static String CONCURRENCY_THREAD_GROUP = "com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup"; + + @Test + public void parse() throws Exception { + File file = new File("/Users/dhf/Desktop/Concurrency Thread Group.jmx"); + + final FileInputStream inputStream = new FileInputStream(file); + final InputSource inputSource = new InputSource(inputStream); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + final Document document = docBuilder.parse(inputSource); + + final Element jmeterTestPlan = document.getDocumentElement(); + + NodeList childNodes = jmeterTestPlan.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + + if (node instanceof Element) { + Element ele = (Element) node; + + // jmeterTestPlan的子元素肯定是 + parseHashTree(ele); + } + } + + + DOMSource domSource = new DOMSource(document); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.transform(domSource, result); + System.out.println("XML IN String format is: \n" + writer.toString()); + } + + private void parseHashTree(Element hashTree) { + if (!valid(hashTree)) { + return; + } + + if (hashTree.getChildNodes().getLength() > 0) { + final NodeList childNodes = hashTree.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element) { + Element ele = (Element) node; + if (!valid(ele)) { + continue; + } + + if (nodeNameEquals(ele, HASH_TREE_ELEMENT)) { + parseHashTree(ele); + } else if (nodeNameEquals(ele, CONCURRENCY_THREAD_GROUP)) { + processConcurrencyThreadGroup(ele); + } + } + } + } + } + + private void processConcurrencyThreadGroup(Element concurrencyThreadGroup) { + if (concurrencyThreadGroup.getChildNodes().getLength() > 0) { + final NodeList childNodes = concurrencyThreadGroup.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element) { + Element ele = (Element) node; + if (!valid(ele)) { + continue; + } + + if (nodeNameEquals(ele, STRING_PROP)) { + parseStringProp(ele); + } + } + } + } + } + + private void parseStringProp(Element stringProp) { + if (stringProp.getChildNodes().getLength() > 0) { + stringProp.getFirstChild().setNodeValue("1"); + } + } + + private boolean nodeNameEquals(Node node, String desiredName) { + return desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()); + } + + private boolean valid(Element ele) { + return StringUtils.isBlank(ele.getAttribute("enabled")) || Boolean.parseBoolean(ele.getAttribute("enabled")); + } +}