From 6bdb0e7386cbf7e6835274187ed5a3fbe907cdf0 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 29 Apr 2021 18:57:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96):=20=E4=BF=AE=E5=A4=8Dcsv=20=E5=B9=B6=E8=A1=8C?= =?UTF-8?q?=E5=8F=96=E5=80=BC=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/definition/request/MsTestElement.java | 4 +- .../api/service/ApiAutomationService.java | 5 +- .../api/service/ApiTestCaseService.java | 23 +- .../org/apache/jmeter/config/CSVDataSet.java | 346 +++++++++++ .../apache/jmeter/services/FileServer.java | 582 ------------------ frontend/src/business/components/xpack | 2 +- 6 files changed, 367 insertions(+), 595 deletions(-) create mode 100644 backend/src/main/java/org/apache/jmeter/config/CSVDataSet.java delete mode 100644 backend/src/main/java/org/apache/jmeter/services/FileServer.java diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java index 510e4e3395..c1fd881aad 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java @@ -205,8 +205,8 @@ public abstract class MsTestElement { csvDataSet.setProperty("filename", BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName()); } csvDataSet.setIgnoreFirstLine(false); - csvDataSet.setRecycle(true); - //csvDataSet.setProperty("shareMode","shareMode.group"); + // csvDataSet.setProperty("quotedData",true); + csvDataSet.setProperty("shareMode","shareMode.group"); csvDataSet.setProperty("recycle", true); csvDataSet.setProperty("delimiter", item.getDelimiter()); csvDataSet.setComment(StringUtils.isEmpty(item.getDescription()) ? "" : item.getDescription()); diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index 1fe94f0da6..dd1d01f7a7 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -61,7 +61,6 @@ import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; - @Service @Transactional(rollbackFor = Exception.class) public class ApiAutomationService { @@ -785,7 +784,7 @@ public class ApiAutomationService { batchMapper.insert(report); // 调用执行方法 - // jMeterService.runDefinition(report.getId(), hashTree, request.getReportId(), request.getRunMode()); + //jMeterService.runDefinition(report.getId(), hashTree, request.getReportId(), request.getRunMode()); map.put(report.getId(), hashTree); // 重置报告ID reportId = UUID.randomUUID().toString(); @@ -794,6 +793,7 @@ public class ApiAutomationService { // 开始执行 ExecutorService executorService = Executors.newFixedThreadPool(map.size()); for (String key : map.keySet()) { + // jMeterService.runDefinition(key, map.get(key), request.getReportId(), request.getRunMode()); executorService.submit(new ParallelExecTask(jMeterService, key, map.get(key), request)); } @@ -1079,7 +1079,6 @@ public class ApiAutomationService { } catch (Exception e) { MSException.throwException(e.getMessage()); } - System.out.println(request.getTestElement().getJmx(hashTree)); // 调用执行方法 APIScenarioReportResult report = createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(), diff --git a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java index aa287aeec1..14b4defb72 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java @@ -163,15 +163,16 @@ public class ApiTestCaseService { public ApiTestCaseWithBLOBs get(String id) { // ApiTestCaseWithBLOBs returnBlobs = apiTestCaseMapper.selectByPrimaryKey(id); ApiTestCaseInfo model = extApiTestCaseMapper.selectApiCaseInfoByPrimaryKey(id); - if(model != null ){ - if(StringUtils.equalsIgnoreCase(model.getApiMethod(),"esb")){ + if (model != null) { + if (StringUtils.equalsIgnoreCase(model.getApiMethod(), "esb")) { esbApiParamService.handleApiEsbParams(model); } } return model; } - public ApiTestCaseInfo getResult(String id){ - return extApiTestCaseMapper.selectApiCaseInfoByPrimaryKey(id); + + public ApiTestCaseInfo getResult(String id) { + return extApiTestCaseMapper.selectApiCaseInfoByPrimaryKey(id); } public ApiTestCase create(SaveApiTestCaseRequest request, List bodyFiles) { @@ -278,7 +279,11 @@ public class ApiTestCaseService { test.setPriority(request.getPriority()); test.setUpdateTime(System.currentTimeMillis()); test.setDescription(request.getDescription()); - test.setTags(request.getTags()); + if (StringUtils.equals("[]", request.getTags())) { + test.setTags(null); + } else { + test.setTags(request.getTags()); + } apiTestCaseMapper.updateByPrimaryKeySelective(test); return test; } @@ -304,7 +309,11 @@ public class ApiTestCaseService { test.setUpdateTime(System.currentTimeMillis()); test.setDescription(request.getDescription()); test.setNum(getNextNum(request.getApiDefinitionId())); - test.setTags(request.getTags()); + if (StringUtils.equals("[]", request.getTags())) { + test.setTags(null); + } else { + test.setTags(request.getTags()); + } apiTestCaseMapper.insert(test); return test; } @@ -476,7 +485,7 @@ public class ApiTestCaseService { public Map getRequest(ApiTestCaseRequest request) { List list = extApiTestCaseMapper.getRequest(request); for (ApiTestCaseInfo model : list) { - if(StringUtils.equalsIgnoreCase(model.getApiMethod(),"esb")){ + if (StringUtils.equalsIgnoreCase(model.getApiMethod(), "esb")) { esbApiParamService.handleApiEsbParams(model); } } diff --git a/backend/src/main/java/org/apache/jmeter/config/CSVDataSet.java b/backend/src/main/java/org/apache/jmeter/config/CSVDataSet.java new file mode 100644 index 0000000000..7fd554f6c5 --- /dev/null +++ b/backend/src/main/java/org/apache/jmeter/config/CSVDataSet.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.config; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.NoConfigMerge; +import org.apache.jmeter.gui.GUIMenuSortOrder; +import org.apache.jmeter.gui.TestElementMetadata; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.jorphan.util.JOrphanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.io.IOException; +import java.util.ResourceBundle; + +/** + * Read lines from a file and split int variables. + *

+ * The iterationStart() method is used to set up each set of values. + *

+ * By default, the same file is shared between all threads + * (and other thread groups, if they use the same file name). + *

+ * The shareMode can be set to: + *

    + *
  • All threads - default, as described above
  • + *
  • Current thread group
  • + *
  • Current thread
  • + *
  • Identifier - all threads sharing the same identifier
  • + *
+ *

+ * The class uses the FileServer alias mechanism to provide the different share modes. + * For all threads, the file alias is set to the file name. + * Otherwise, a suffix is appended to the filename to make it unique within the required context. + * For current thread group, the thread group identityHashcode is used; + * for individual threads, the thread hashcode is used as the suffix. + * Or the user can provide their own suffix, in which case the file is shared between all + * threads with the same suffix. + */ +@GUIMenuSortOrder(1) +@TestElementMetadata(labelResource = "displayName") +public class CSVDataSet extends ConfigTestElement + implements TestBean, LoopIterationListener, NoConfigMerge { + private static final Logger log = LoggerFactory.getLogger(CSVDataSet.class); + + private static final long serialVersionUID = 233L; + + private static final String EOFVALUE = // value to return at EOF + JMeterUtils.getPropDefault("csvdataset.eofstring", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + private transient String filename; + + private transient String fileEncoding; + + private transient String variableNames; + + private transient String delimiter; + + private transient boolean quoted; + + private transient boolean recycle = true; + + private transient boolean stopThread; + + private transient String[] vars; + + private transient String alias; + + private transient String shareMode; + + private boolean firstLineIsNames = false; + + private boolean ignoreFirstLine = false; + + private Object readResolve() { + recycle = true; + return this; + } + + /** + * Override the setProperty method in order to convert + * the original String shareMode property. + * This used the locale-dependent display value, so caused + * problems when the language was changed. + * If the "shareMode" value matches a resource value then it is converted + * into the resource key. + * To reduce the need to look up resources, we only attempt to + * convert values with spaces in them, as these are almost certainly + * not variables (and they are definitely not resource keys). + */ + @Override + public void setProperty(JMeterProperty property) { + if (!(property instanceof StringProperty)) { + super.setProperty(property); + return; + } + + final String propName = property.getName(); + if (!"shareMode".equals(propName)) { + super.setProperty(property); + return; + } + + final String propValue = property.getStringValue(); + if (propValue.contains(" ")) { // variables are unlikely to contain spaces, so most likely a translation + try { + final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass()); + final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE); + for (String resKey : CSVDataSetBeanInfo.getShareTags()) { + if (propValue.equals(rb.getString(resKey))) { + if (log.isDebugEnabled()) { + log.debug("Converted {}={} to {} using Locale: {}", propName, propValue, resKey, rb.getLocale()); + } + ((StringProperty) property).setValue(resKey); // reset the value + super.setProperty(property); + return; + } + } + // This could perhaps be a variable name + log.warn("Could not translate {}={} using Locale: {}", propName, propValue, rb.getLocale()); + } catch (IntrospectionException e) { + log.error("Could not find BeanInfo; cannot translate shareMode entries", e); + } + } + super.setProperty(property); + } + + @Override + public void iterationStart(LoopIterationEvent iterEvent) { + FileServer server = FileServer.getFileServer(); + final JMeterContext context = getThreadContext(); + String delim = getDelimiter(); + if ("\\t".equals(delim)) { // $NON-NLS-1$ + delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$ + } else if (delim.isEmpty()) { + log.debug("Empty delimiter, will use ','"); + delim = ","; + } + //if (vars == null) { + initVars(server, context, delim); + //} + + // TODO: fetch this once as per vars above? + JMeterVariables threadVars = context.getVariables(); + String[] lineValues = {}; + try { + if (getQuotedData()) { + lineValues = server.getParsedLine(alias, recycle, + firstLineIsNames || ignoreFirstLine, delim.charAt(0)); + } else { + String line = server.readLine(alias, recycle, + firstLineIsNames || ignoreFirstLine); + lineValues = JOrphanUtils.split(line, delim, false); + } + for (int a = 0; a < vars.length && a < lineValues.length; a++) { + threadVars.put(vars[a], lineValues[a]); + } + } catch (IOException e) { // treat the same as EOF + log.error(e.toString()); + } + if (lineValues.length == 0) {// i.e. EOF + if (getStopThread()) { + throw new JMeterStopThreadException("End of file:" + getFilename() + " detected for CSV DataSet:" + + getName() + " configured with stopThread:" + getStopThread() + ", recycle:" + getRecycle()); + } + for (String var : vars) { + threadVars.put(var, EOFVALUE); + } + } + } + + private void initVars(FileServer server, final JMeterContext context, String delim) { + String fileName = getFilename().trim(); + setAlias(context, fileName); + final String names = getVariableNames(); + if (StringUtils.isEmpty(names)) { + String header = server.reserveFile(fileName, getFileEncoding(), alias, true); + try { + vars = CSVSaveService.csvSplitString(header, delim.charAt(0)); + firstLineIsNames = true; + } catch (IOException e) { + throw new IllegalArgumentException("Could not split CSV header line from file:" + fileName, e); + } + } else { + server.reserveFile(fileName, getFileEncoding(), alias, ignoreFirstLine); + vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$ + } + trimVarNames(vars); + } + + private void setAlias(final JMeterContext context, String alias) { + String mode = getShareMode(); + int modeInt = CSVDataSetBeanInfo.getShareModeAsInt(mode); + switch (modeInt) { + case CSVDataSetBeanInfo.SHARE_ALL: + this.alias = alias; + break; + case CSVDataSetBeanInfo.SHARE_GROUP: + this.alias = alias + "@" + System.identityHashCode(context.getThreadGroup()); + break; + case CSVDataSetBeanInfo.SHARE_THREAD: + this.alias = alias + "@" + System.identityHashCode(context.getThread()); + break; + default: + this.alias = alias + "@" + mode; // user-specified key + break; + } + } + + /** + * trim content of array varNames + * + * @param varsNames + */ + private void trimVarNames(String[] varsNames) { + for (int i = 0; i < varsNames.length; i++) { + varsNames[i] = varsNames[i].trim(); + } + } + + /** + * @return Returns the filename. + */ + public String getFilename() { + return filename; + } + + /** + * @param filename The filename to set. + */ + public void setFilename(String filename) { + this.filename = filename; + } + + /** + * @return Returns the file encoding. + */ + public String getFileEncoding() { + return fileEncoding; + } + + /** + * @param fileEncoding The fileEncoding to set. + */ + public void setFileEncoding(String fileEncoding) { + this.fileEncoding = fileEncoding; + } + + /** + * @return Returns the variableNames. + */ + public String getVariableNames() { + return variableNames; + } + + /** + * @param variableNames The variableNames to set. + */ + public void setVariableNames(String variableNames) { + this.variableNames = variableNames; + } + + public String getDelimiter() { + return delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + public boolean getQuotedData() { + return quoted; + } + + public void setQuotedData(boolean quoted) { + this.quoted = quoted; + } + + public boolean getRecycle() { + return recycle; + } + + public void setRecycle(boolean recycle) { + this.recycle = recycle; + } + + public boolean getStopThread() { + return stopThread; + } + + public void setStopThread(boolean value) { + this.stopThread = value; + } + + public String getShareMode() { + return shareMode; + } + + public void setShareMode(String value) { + this.shareMode = value; + } + + /** + * @return the ignoreFirstLine + */ + public boolean isIgnoreFirstLine() { + return ignoreFirstLine; + } + + /** + * @param ignoreFirstLine the ignoreFirstLine to set + */ + public void setIgnoreFirstLine(boolean ignoreFirstLine) { + this.ignoreFirstLine = ignoreFirstLine; + } +} diff --git a/backend/src/main/java/org/apache/jmeter/services/FileServer.java b/backend/src/main/java/org/apache/jmeter/services/FileServer.java deleted file mode 100644 index d51e9a836b..0000000000 --- a/backend/src/main/java/org/apache/jmeter/services/FileServer.java +++ /dev/null @@ -1,582 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jmeter.services; - -import org.apache.commons.io.input.BOMInputStream; -import org.apache.jmeter.gui.JMeterFileFilter; -import org.apache.jmeter.save.CSVSaveService; -import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.util.JOrphanUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.nio.file.Files; -import java.util.ArrayDeque; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; - -/** - * This class provides thread-safe access to files, and to - * provide some simplifying assumptions about where to find files and how to - * name them. For instance, putting supporting files in the same directory as - * the saved test plan file allows users to refer to the file with just it's - * name - this FileServer class will find the file without a problem. - * Eventually, I want all in-test file access to be done through here, with the - * goal of packaging up entire test plans as a directory structure that can be - * sent via rmi to remote servers (currently, one must make sure the remote - * server has all support files in a relative-same location) and to package up - * test plans to execute on unknown boxes that only have Java installed. - */ -public class FileServer { - - private static final Logger log = LoggerFactory.getLogger(FileServer.class); - - /** - * The default base used for resolving relative files, i.e.
- * {@code System.getProperty("user.dir")} - */ - private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$ - - /** Default base prefix: {@value} */ - private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$ - - private static final String BASE_PREFIX = - JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$ - BASE_PREFIX_DEFAULT); - - private File base; - - private final Map files = new HashMap<>(); - - private static final FileServer server = new FileServer(); - - // volatile needed to ensure safe publication - private volatile String scriptName; - - // Cannot be instantiated - private FileServer() { - base = new File(DEFAULT_BASE); - log.info("Default base='{}'", DEFAULT_BASE); - } - - /** - * @return the singleton instance of the server. - */ - public static FileServer getFileServer() { - return server; - } - - /** - * Resets the current base to DEFAULT_BASE. - */ - public synchronized void resetBase() { - checkForOpenFiles(); - base = new File(DEFAULT_BASE); - log.info("Reset base to '{}'", base); - } - - /** - * Sets the current base directory for relative file names from the provided path. - * If the path does not refer to an existing directory, then its parent is used. - * Normally the provided path is a file, so using the parent directory is appropriate. - * - * @param basedir the path to set, or {@code null} if the GUI is being cleared - * @throws IllegalStateException if files are still open - */ - public synchronized void setBasedir(String basedir) { - checkForOpenFiles(); // TODO should this be called if basedir == null? - if (basedir != null) { - File newBase = new File(basedir); - if (!newBase.isDirectory()) { - newBase = newBase.getParentFile(); - } - base = newBase; - log.info("Set new base='{}'", base); - } - } - - /** - * Sets the current base directory for relative file names from the provided script file. - * The parameter is assumed to be the path to a JMX file, so the base directory is derived - * from its parent. - * - * @param scriptPath the path of the script file; must be not be {@code null} - * @throws IllegalStateException if files are still open - * @throws IllegalArgumentException if scriptPath parameter is null - */ - public synchronized void setBaseForScript(File scriptPath) { - if (scriptPath == null){ - throw new IllegalArgumentException("scriptPath must not be null"); - } - setScriptName(scriptPath.getName()); - // getParentFile() may not work on relative paths - setBase(scriptPath.getAbsoluteFile().getParentFile()); - } - - /** - * Sets the current base directory for relative file names. - * - * @param jmxBase the path of the script file base directory, cannot be null - * @throws IllegalStateException if files are still open - * @throws IllegalArgumentException if {@code basepath} is null - */ - public synchronized void setBase(File jmxBase) { - if (jmxBase == null) { - throw new IllegalArgumentException("jmxBase must not be null"); - } - checkForOpenFiles(); - base = jmxBase; - log.info("Set new base='{}'", base); - } - - /** - * Check if there are entries in use. - *

- * Caller must ensure that access to the files map is single-threaded as - * there is a window between checking the files Map and clearing it. - * - * @throws IllegalStateException if there are any entries still in use - */ - private void checkForOpenFiles() throws IllegalStateException { - if (filesOpen()) { // checks for entries in use - throw new IllegalStateException("Files are still open, cannot change base directory"); - } - files.clear(); // tidy up any unused entries - } - - public synchronized String getBaseDir() { - return base.getAbsolutePath(); - } - - public static String getDefaultBase(){ - return DEFAULT_BASE; - } - - /** - * Calculates the relative path from DEFAULT_BASE to the current base, - * which must be the same as or a child of the default. - * - * @return the relative path, or {@code "."} if the path cannot be determined - */ - public synchronized File getBaseDirRelative() { - // Must first convert to absolute path names to ensure parents are available - File parent = new File(DEFAULT_BASE).getAbsoluteFile(); - File f = base.getAbsoluteFile(); - ArrayDeque l = new ArrayDeque<>(); - while (f != null) { - if (f.equals(parent)){ - if (l.isEmpty()){ - break; - } - File rel = new File(l.pop()); - while(!l.isEmpty()) { - rel = new File(rel, l.pop()); - } - return rel; - } - l.push(f.getName()); - f = f.getParentFile(); - } - return new File("."); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null) - */ - public void reserveFile(String filename) { - reserveFile(filename,null); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null) - * @param charsetName - the character set encoding to use for the file (may be null) - */ - public void reserveFile(String filename, String charsetName) { - reserveFile(filename, charsetName, filename, false); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null) - * @param charsetName - the character set encoding to use for the file (may be null) - * @param alias - the name to be used to access the object (must not be null) - */ - public void reserveFile(String filename, String charsetName, String alias) { - reserveFile(filename, charsetName, alias, false); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null or empty) - * @param charsetName - the character set encoding to use for the file (may be null) - * @param alias - the name to be used to access the object (must not be null) - * @param hasHeader true if the file has a header line describing the contents - * @return the header line; may be null - * @throws IllegalArgumentException if header could not be read or filename is null or empty - */ - public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) { - if (filename == null || filename.isEmpty()){ - throw new IllegalArgumentException("Filename must not be null or empty"); - } - if (alias == null){ - throw new IllegalArgumentException("Alias must not be null"); - } - FileEntry fileEntry = files.get(alias); - if (fileEntry == null) { - fileEntry = new FileEntry(resolveFileFromPath(filename), null, charsetName); - if (filename.equals(alias)){ - log.info("Stored: {}", filename); - } else { - log.info("Stored: {} Alias: {}", filename, alias); - } - files.put(alias, fileEntry); - if (hasHeader) { - try { - fileEntry.headerLine = readLine(alias, false); - if (fileEntry.headerLine == null) { - fileEntry.exception = new EOFException("File is empty: " + fileEntry.file); - } - } catch (IOException | IllegalArgumentException e) { - fileEntry.exception = e; - } - } - } - if (hasHeader && fileEntry.headerLine == null) { - throw new IllegalArgumentException("Could not read file header line for file " + filename, - fileEntry.exception); - } - return fileEntry.headerLine; - } - - /** - * Resolves file name into {@link File} instance. - * When filename is not absolute and not found from current working dir, - * it tries to find it under current base directory - * @param filename original file name - * @return {@link File} instance - */ - private File resolveFileFromPath(String filename) { - File f = new File(filename); - if (f.isAbsolute() || f.exists()) { - return f; - } else { - return new File(base, filename); - } - } - - /** - * Get the next line of the named file, recycle by default. - * - * @param filename the filename or alias that was used to reserve the file - * @return String containing the next line in the file - * @throws IOException when reading of the file fails, or the file was not reserved properly - */ - public String readLine(String filename) throws IOException { - return readLine(filename, true); - } - - /** - * Get the next line of the named file, first line is name to false - * - * @param filename the filename or alias that was used to reserve the file - * @param recycle - should file be restarted at EOF? - * @return String containing the next line in the file (null if EOF reached and not recycle) - * @throws IOException when reading of the file fails, or the file was not reserved properly - */ - public String readLine(String filename, boolean recycle) throws IOException { - return readLine(filename, recycle, false); - } - /** - * Get the next line of the named file - * - * @param filename the filename or alias that was used to reserve the file - * @param recycle - should file be restarted at EOF? - * @param ignoreFirstLine - Ignore first line - * @return String containing the next line in the file (null if EOF reached and not recycle) - * @throws IOException when reading of the file fails, or the file was not reserved properly - */ - public synchronized String readLine(String filename, boolean recycle, - boolean ignoreFirstLine) throws IOException { - FileEntry fileEntry = files.get(filename); - if(fileEntry == null ){ - this.reserveFile(filename); - fileEntry = files.get(filename); - } - if (fileEntry != null) { - if (fileEntry.inputOutputObject == null) { - fileEntry.inputOutputObject = createBufferedReader(fileEntry); - } else if (!(fileEntry.inputOutputObject instanceof Reader)) { - throw new IOException("File " + filename + " already in use"); - } - BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject; - String line = reader.readLine(); - if (line == null && recycle) { - reader.close(); - reader = createBufferedReader(fileEntry); - fileEntry.inputOutputObject = reader; - if (ignoreFirstLine) { - // read first line and forget - reader.readLine();//NOSONAR - } - line = reader.readLine(); - } - log.debug("Read:{}", line); - return line; - } - throw new IOException("File never reserved: "+filename); - } - - /** - * - * @param alias the file name or alias - * @param recycle whether the file should be re-started on EOF - * @param ignoreFirstLine whether the file contains a file header which will be ignored - * @param delim the delimiter to use for parsing - * @return the parsed line, will be empty if the file is at EOF - * @throws IOException when reading of the aliased file fails, or the file was not reserved properly - */ - public synchronized String[] getParsedLine(String alias, boolean recycle, boolean ignoreFirstLine, char delim) throws IOException { - BufferedReader reader = getReader(alias, recycle, ignoreFirstLine); - return CSVSaveService.csvReadFile(reader, delim); - } - - /** - * Return BufferedReader handling close if EOF reached and recycle is true - * and ignoring first line if ignoreFirstLine is true - * - * @param alias String alias - * @param recycle Recycle at eof - * @param ignoreFirstLine Ignore first line - * @return {@link BufferedReader} - */ - private BufferedReader getReader(String alias, boolean recycle, boolean ignoreFirstLine) throws IOException { - FileEntry fileEntry = files.get(alias); - if (fileEntry != null) { - BufferedReader reader; - if (fileEntry.inputOutputObject == null) { - reader = createBufferedReader(fileEntry); - fileEntry.inputOutputObject = reader; - if (ignoreFirstLine) { - // read first line and forget - reader.readLine(); //NOSONAR - } - } else if (!(fileEntry.inputOutputObject instanceof Reader)) { - throw new IOException("File " + alias + " already in use"); - } else { - reader = (BufferedReader) fileEntry.inputOutputObject; - if (recycle) { // need to check if we are at EOF already - reader.mark(1); - int peek = reader.read(); - if (peek == -1) { // already at EOF - reader.close(); - reader = createBufferedReader(fileEntry); - fileEntry.inputOutputObject = reader; - if (ignoreFirstLine) { - // read first line and forget - reader.readLine(); //NOSONAR - } - } else { // OK, we still have some data, restore it - reader.reset(); - } - } - } - return reader; - } else { - throw new IOException("File never reserved: "+alias); - } - } - - private BufferedReader createBufferedReader(FileEntry fileEntry) throws IOException { - if (!fileEntry.file.canRead() || !fileEntry.file.isFile()) { - throw new IllegalArgumentException("File "+ fileEntry.file.getName()+ " must exist and be readable"); - } - BOMInputStream fis = new BOMInputStream(Files.newInputStream(fileEntry.file.toPath())); //NOSONAR - InputStreamReader isr = null; - // If file encoding is specified, read using that encoding, otherwise use default platform encoding - String charsetName = fileEntry.charSetEncoding; - if(!JOrphanUtils.isBlank(charsetName)) { - isr = new InputStreamReader(fis, charsetName); - } else if (fis.hasBOM()) { - isr = new InputStreamReader(fis, fis.getBOM().getCharsetName()); - } else { - @SuppressWarnings("DefaultCharset") - final InputStreamReader withPlatformEncoding = new InputStreamReader(fis); - isr = withPlatformEncoding; - } - return new BufferedReader(isr); - } - - public synchronized void write(String filename, String value) throws IOException { - FileEntry fileEntry = files.get(filename); - if (fileEntry != null) { - if (fileEntry.inputOutputObject == null) { - fileEntry.inputOutputObject = createBufferedWriter(fileEntry); - } else if (!(fileEntry.inputOutputObject instanceof Writer)) { - throw new IOException("File " + filename + " already in use"); - } - BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject; - log.debug("Write:{}", value); - writer.write(value); - } else { - throw new IOException("File never reserved: "+filename); - } - } - - private BufferedWriter createBufferedWriter(FileEntry fileEntry) throws IOException { - OutputStream fos = Files.newOutputStream(fileEntry.file.toPath()); - OutputStreamWriter osw; - // If file encoding is specified, write using that encoding, otherwise use default platform encoding - String charsetName = fileEntry.charSetEncoding; - if(!JOrphanUtils.isBlank(charsetName)) { - osw = new OutputStreamWriter(fos, charsetName); - } else { - @SuppressWarnings("DefaultCharset") - final OutputStreamWriter withPlatformEncoding = new OutputStreamWriter(fos); - osw = withPlatformEncoding; - } - return new BufferedWriter(osw); - } - - public synchronized void closeFiles() throws IOException { - for (Map.Entry me : files.entrySet()) { - closeFile(me.getKey(),me.getValue() ); - } - files.clear(); - } - - /** - * @param name the name or alias of the file to be closed - * @throws IOException when closing of the aliased file fails - */ - public synchronized void closeFile(String name) throws IOException { - FileEntry fileEntry = files.get(name); - closeFile(name, fileEntry); - } - - private void closeFile(String name, FileEntry fileEntry) throws IOException { - if (fileEntry != null && fileEntry.inputOutputObject != null) { - log.info("Close: {}", name); - fileEntry.inputOutputObject.close(); - fileEntry.inputOutputObject = null; - } - } - - boolean filesOpen() { // package access for test code only - return files.values().stream() - .anyMatch(fileEntry -> fileEntry.inputOutputObject != null); - } - - /** - * Method will get a random file in a base directory - *

- * TODO hey, not sure this method belongs here. - * FileServer is for thread safe File access relative to current test's base directory. - * - * @param basedir name of the directory in which the files can be found - * @param extensions array of allowed extensions, if null is given, - * any file be allowed - * @return a random File from the basedir that matches one of - * the extensions - */ - public File getRandomFile(String basedir, String[] extensions) { - File input = null; - if (basedir != null) { - File src = new File(basedir); - File[] lfiles = src.listFiles(new JMeterFileFilter(extensions)); - if (lfiles != null) { - // lfiles cannot be null as it has been checked before - int count = lfiles.length; - input = lfiles[ThreadLocalRandom.current().nextInt(count)]; - } - } - return input; - } - - /** - * Get {@link File} instance for provided file path, - * resolve file location relative to base dir or script dir when needed - * - * @param path original path to file, maybe relative - * @return {@link File} instance - */ - public File getResolvedFile(String path) { - reserveFile(path); - return files.get(path).file; - } - - private static class FileEntry{ - private String headerLine; - private Throwable exception; - private final File file; - private Closeable inputOutputObject; - private final String charSetEncoding; - - FileEntry(File f, Closeable o, String e) { - file = f; - inputOutputObject = o; - charSetEncoding = e; - } - } - - /** - * Resolve a file name that may be relative to the base directory. If the - * name begins with the value of the JMeter property - * "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is - * assumed to be relative to the basename. - * - * @param relativeName - * filename that should be checked for - * jmeter.save.saveservice.base_prefix - * @return the updated filename - */ - public static String resolveBaseRelativeName(String relativeName) { - if (relativeName.startsWith(BASE_PREFIX)){ - String newName = relativeName.substring(BASE_PREFIX.length()); - return new File(getFileServer().getBaseDir(),newName).getAbsolutePath(); - } - return relativeName; - } - - /** - * @return JMX Script name - * @since 2.6 - */ - public String getScriptName() { - return scriptName; - } - - /** - * @param scriptName Script name - * @since 2.6 - */ - public void setScriptName(String scriptName) { - this.scriptName = scriptName; - } -} diff --git a/frontend/src/business/components/xpack b/frontend/src/business/components/xpack index e50f046382..e7709b9a34 160000 --- a/frontend/src/business/components/xpack +++ b/frontend/src/business/components/xpack @@ -1 +1 @@ -Subproject commit e50f0463826ac4d7837ea3a237333827774a1b19 +Subproject commit e7709b9a340394e78610b91105b2cec0f1b8289d