fix: add property bindings to reduce code duplication in UI classes and make "configure and modifyTestElement" simple in most cases
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java b/src/core/src/main/java/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java
index f9920f1..4c6a791 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/AbstractJMeterGuiComponent.java
@@ -35,9 +35,10 @@
import javax.swing.JTextField;
import javax.swing.border.Border;
+import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.testelement.TestElement;
-import org.apache.jmeter.testelement.property.StringProperty;
+import org.apache.jmeter.testelement.TestElementSchema;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.Printable;
import org.apache.jorphan.gui.JFactory;
@@ -84,6 +85,13 @@
private final JTextArea commentField = JFactory.tabMovesFocus(new JTextArea());
/**
+ * Stores a collection of property editors, so GuiCompoenent can have default implementations that
+ * update the UI fields based on {@link TestElement} properties and vice versa.
+ */
+ @API(status = EXPERIMENTAL, since = "5.6.3")
+ protected final BindingGroup bindingGroup = new BindingGroup();
+
+ /**
* When constructing a new component, this takes care of basic tasks like
* setting up the Name Panel and assigning the class's static label as the
* name to start.
@@ -205,6 +213,7 @@
setName(element.getName());
enabled = element.isEnabled();
commentField.setText(element.getComment());
+ bindingGroup.updateUi(element);
}
/**
@@ -228,6 +237,14 @@
initGui();
}
+ @Override
+ @API(status = EXPERIMENTAL, since = "5.6.3")
+ public void modifyTestElement(TestElement element) {
+ JMeterGUIComponent.super.modifyTestElement(element);
+ modifyTestElementEnabledAndComment(element);
+ bindingGroup.updateElement(element);
+ }
+
/**
* This provides a convenience for extenders when they implement the
* {@link JMeterGUIComponent#modifyTestElement(TestElement)} method. This
@@ -235,21 +252,37 @@
* Element. It should be called by every extending class when
* creating/modifying Test Elements, as that will best assure consistent
* behavior.
+ * <p>Deprecation notice: most likely you do not need the method, and you should
+ * override {@link #modifyTestElement(TestElement)} instead</p>
*
* @param mc
* the TestElement being created.
*/
+ @API(status = DEPRECATED, since = "5.6.3")
protected void configureTestElement(TestElement mc) {
- mc.setName(getName());
+ mc.setName(StringUtils.defaultIfEmpty(getName(), null));
+ TestElementSchema schema = TestElementSchema.INSTANCE;
+ mc.set(schema.getGuiClass(), getClass());
+ mc.set(schema.getTestClass(), mc.getClass());
+ modifyTestElementEnabledAndComment(mc);
+ }
- mc.setProperty(new StringProperty(TestElement.GUI_CLASS, this.getClass().getName()));
-
- mc.setProperty(new StringProperty(TestElement.TEST_CLASS, mc.getClass().getName()));
-
+ /**
+ * Assigns basic fields from UI to the test element: name, comments, gui class, and the registered editors.
+ *
+ * @param mc test element
+ */
+ private void modifyTestElementEnabledAndComment(TestElement mc) {
// This stores the state of the TestElement
log.debug("setting element to enabled: {}", enabled);
- mc.setEnabled(enabled);
- mc.setComment(getComment());
+ // We can skip storing "enabled" state if it's true, as it's default value.
+ // JMeter removes disabled elements early from the tree, so configuration elements
+ // with enabled=false (~HTTP Request Defaults) can't unexpectedly override the regular ones
+ // like HTTP Request.
+ mc.set(TestElementSchema.INSTANCE.getEnabled(), enabled ? null : Boolean.FALSE);
+ // Note: we can't use editors for "comments" as getComments() is not a final method, so plugins might
+ // override it and provide a different implementation.
+ mc.setComment(StringUtils.defaultIfEmpty(getComment(), null));
}
/**
diff --git a/src/core/src/main/java/org/apache/jmeter/gui/JMeterGUIComponent.java b/src/core/src/main/java/org/apache/jmeter/gui/JMeterGUIComponent.java
index 17b5676..c7df480 100644
--- a/src/core/src/main/java/org/apache/jmeter/gui/JMeterGUIComponent.java
+++ b/src/core/src/main/java/org/apache/jmeter/gui/JMeterGUIComponent.java
@@ -21,7 +21,10 @@
import javax.swing.JPopupMenu;
+import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.testelement.TestElement;
+import org.apache.jmeter.testelement.TestElementSchema;
+import org.apiguardian.api.API;
/**
* Implementing this interface indicates that the class is a JMeter GUI
@@ -82,9 +85,9 @@
* the component's label in the local language. The resource name is fixed,
* and does not vary with the selected language.
*
- * Normally this method should be overridden in preference to overriding
+ * <p>Normally this method should be overridden in preference to overriding
* getStaticLabel(). However where the resource name is not available or required,
- * getStaticLabel() may be overridden instead.
+ * getStaticLabel() may be overridden instead.</p>
*
* @return the resource name
*/
@@ -105,8 +108,19 @@
* the model class that it knows how to display, and this method is called
* when new test elements are created.
*
+ * <p>Since 5.6.3, the default implementation is as follows, and subclasses should override
+ * {@link #makeTestElement()}
+ * <pre>
+ * public TestElement createTestElement() {
+ * TestElement element = makeTestElement();
+ * assignDefaultValues(element);
+ * return el;
+ * }
+ * </pre>
+ *
* <p>
- * The canonical implementation looks like this:
+ * Before 5.6.3 the canonical implementation was as follows, however, it is recommended to
+ * avoid overriding {@link #createTestElement()} and override {@link #makeTestElement()} instead.
* <pre>
* public TestElement createTestElement() {
* TestElementXYZ el = new TestElementXYZ();
@@ -117,7 +131,35 @@
*
* @return the Test Element object that the GUI component represents.
*/
- TestElement createTestElement();
+ @API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+ default TestElement createTestElement() {
+ TestElement element = makeTestElement();
+ assignDefaultValues(element);
+ return element;
+ }
+
+ /**
+ * Creates the test element represented by the GUI component.
+ * @since 5.6.3
+ * @return a new {@link TestElement}
+ */
+ @API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+ default TestElement makeTestElement() {
+ throw new UnsupportedOperationException("Please override makeTestElement with creating the element you need: return new ....");
+ }
+
+ /**
+ * Configures default values for element after its creation.
+ * Plugin authors should call this once in their {@link #createTestElement()} implementation.
+ * @since 5.6.3
+ * @param element test element to configure
+ */
+ default void assignDefaultValues(TestElement element) {
+ element.setName(StringUtils.defaultIfEmpty(getStaticLabel(), null));
+ TestElementSchema schema = TestElementSchema.INSTANCE;
+ element.set(schema.getGuiClass(), getClass());
+ element.set(schema.getTestClass(), element.getClass());
+ }
/**
* GUI components are responsible for populating TestElements they create
@@ -127,19 +169,26 @@
* information.
*
* <p>
+ * If you override {@link AbstractJMeterGuiComponent}, you might want using {@link AbstractJMeterGuiComponent#bindingGroup}
+ * instead of overriding {@code modifyTestElement}.
+ *
+ * <p>
* The canonical implementation looks like this:
* <pre>
+ * @Override
* public void modifyTestElement(TestElement element) {
- * element.clear(); // many implementations use this
- * configureTestElement(element);
+ * super.modifyTestElement(element); // clear the element and assign basic fields like name, gui class, test class
* // Using the element setters (preferred):
+ * // If the field is empty, you probably want to remove the property instead of storing an empty string
+ * // See <a href="https://github.com/apache/jmeter/pull/6199">Streamline binding of UI elements to TestElement properties</a>
+ * // for more details
* TestElementXYZ xyz = (TestElementXYZ) element;
- * xyz.setState(guiState.getText());
- * xyz.setCode(guiCode.getText());
+ * xyz.setState(StringUtils.defaultIfEmpty(guiState.getText(), null));
+ * xyz.setCode(StringUtils.defaultIfEmpty(guiCode.getText(), null));
* ... other GUI fields ...
* // or directly (do not use unless there is no setter for the field):
- * element.setProperty(TestElementXYZ.STATE, guiState.getText())
- * element.setProperty(TestElementXYZ.CODE, guiCode.getText())
+ * element.setProperty(TestElementXYZ.STATE, StringUtils.defaultIfEmpty(guiState.getText(), null))
+ * element.setProperty(TestElementXYZ.CODE, StringUtils.defaultIfEmpty(guiCode.getText(), null))
* ... other GUI fields ...
* }
* </pre>
@@ -147,7 +196,16 @@
* @param element
* the TestElement to modify
*/
- void modifyTestElement(TestElement element);
+ @API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+ default void modifyTestElement(TestElement element) {
+ // TODO: should we keep .clear() here? It probably makes it easier to remove all properties before populating
+ // the values from UI, however, it might be inefficient.
+ element.clear();
+ element.setName(StringUtils.defaultIfEmpty(getName(), null));
+ TestElementSchema schema = TestElementSchema.INSTANCE;
+ element.set(schema.getGuiClass(), getClass());
+ element.set(schema.getTestClass(), element.getClass());
+ }
/**
* Test GUI elements can be disabled, in which case they do not become part
diff --git a/src/core/src/main/kotlin/org/apache/jmeter/dsl/DslPrinterTraverser.kt b/src/core/src/main/kotlin/org/apache/jmeter/dsl/DslPrinterTraverser.kt
index a170222..d52faa3 100644
--- a/src/core/src/main/kotlin/org/apache/jmeter/dsl/DslPrinterTraverser.kt
+++ b/src/core/src/main/kotlin/org/apache/jmeter/dsl/DslPrinterTraverser.kt
@@ -115,11 +115,9 @@
if (prop == TestElementSchema.testClass && stringValue == te::class.java.name && canSkipTestClass) {
continue
}
- if ((property is StringProperty && stringValue.isNullOrEmpty()) ||
- stringValue == prop?.defaultValueAsString
- ) {
- continue
- }
+ // It might be tempting to skip printing the property if its value matches the default value,
+ // However, it would be wrong because "unset" values might be overriden by "... Request Defaults",
+ // so we do not want accidental overrides if the user explicitly set some of the properties
}
if (prop == null) {
diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/Binding.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/Binding.kt
new file mode 100644
index 0000000..d3aca2e
--- /dev/null
+++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/Binding.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.gui
+
+import org.apache.jmeter.testelement.TestElement
+import org.apache.jmeter.testelement.schema.PropertyDescriptor
+import org.apiguardian.api.API
+
+/**
+ * Binds a UI control to a [PropertyDescriptor], so JMeter can automatically update the test element
+ * from the UI state and vice versa.
+ * @since 5.6.3
+ */
+@API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+public interface Binding {
+ /**
+ * Update [TestElement] based on the state of the UI.
+ * @param testElement element to update
+ */
+ public fun updateElement(testElement: TestElement)
+
+ /**
+ * Update UI based on the state of the given [TestElement].
+ * @param testElement element to get the state from
+ */
+ public fun updateUi(testElement: TestElement)
+}
diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/BindingGroup.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/BindingGroup.kt
new file mode 100644
index 0000000..9af3ce5
--- /dev/null
+++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/BindingGroup.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.gui
+
+import org.apache.jmeter.testelement.TestElement
+import org.apiguardian.api.API
+
+/**
+ * Manages a collection of [Binding]s.
+ * It enables to update a [TestElement] from the UI and vice versa with a common implementation.
+ * @since 5.6.3
+ */
+@API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+public class BindingGroup() : Binding {
+ private val bindings = mutableListOf<Binding>()
+
+ public constructor(bindings: Collection<Binding>) : this() {
+ addAll(bindings)
+ }
+
+ public fun add(binding: Binding) {
+ bindings += binding
+ }
+
+ public fun addAll(bindings: Collection<Binding>) {
+ this.bindings.addAll(bindings)
+ }
+
+ override fun updateElement(testElement: TestElement) {
+ bindings.forEach { it.updateElement(testElement) }
+ }
+
+ override fun updateUi(testElement: TestElement) {
+ bindings.forEach { it.updateUi(testElement) }
+ }
+}
diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/FilePanelEntryBinding.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/FilePanelEntryBinding.kt
new file mode 100644
index 0000000..0741458
--- /dev/null
+++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/FilePanelEntryBinding.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.gui
+
+import org.apache.jmeter.gui.util.FilePanelEntry
+import org.apache.jmeter.testelement.TestElement
+import org.apache.jmeter.testelement.schema.PropertyDescriptor
+import org.apiguardian.api.API
+
+/**
+ * Binds a [FilePanelEntry] to a [PropertyDescriptor], so JMeter can automatically update the test element
+ * from the UI state and vice versa.
+ * @since 5.6.3
+ */
+@API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+public class FilePanelEntryBinding(
+ private val filePanelEntry: FilePanelEntry,
+ private val propertyDescriptor: PropertyDescriptor<*, *>,
+) : Binding {
+ override fun updateElement(testElement: TestElement) {
+ testElement[propertyDescriptor] = filePanelEntry.filename.takeIf { it.isNotEmpty() }
+ }
+
+ override fun updateUi(testElement: TestElement) {
+ filePanelEntry.filename =
+ if (testElement.getPropertyOrNull(propertyDescriptor) == null) {
+ ""
+ } else {
+ testElement.getString(propertyDescriptor)
+ }
+ }
+}
diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt
index 3ff38b3..1b4e70a 100644
--- a/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt
+++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/JBooleanPropertyEditor.kt
@@ -32,7 +32,7 @@
public class JBooleanPropertyEditor(
private val propertyDescriptor: BooleanPropertyDescriptor<*>,
label: String,
-) : JEditableCheckBox(label, DEFAULT_CONFIGURATION) {
+) : JEditableCheckBox(label, DEFAULT_CONFIGURATION), Binding {
private companion object {
@JvmField
val DEFAULT_CONFIGURATION: Configuration = Configuration(
@@ -50,26 +50,18 @@
value = Value.of(propertyDescriptor.defaultValue ?: false)
}
- /**
- * Update [TestElement] based on the state of the UI.
- * TODO: we might better use PropertiesAccessor<TestElement, ElementSchema>
- * However, it would require callers to pass element.getProps() which might allocate.
- * @param testElement element to update
- */
- public fun updateElement(testElement: TestElement) {
+ public override fun updateElement(testElement: TestElement) {
when (val value = value) {
- is Value.Boolean -> testElement[propertyDescriptor] = value.value
+ // For now, UI does not distinguish between "false" and "absent" values,
+ // so we treat "false" as "absent".
+ is Value.Boolean ->
+ testElement[propertyDescriptor] =
+ value.value.takeIf { it || propertyDescriptor.defaultValue == true }
is Value.Text -> testElement[propertyDescriptor] = value.value
}
}
- /**
- * Update UI based on the state of the given [TestElement].
- * TODO: we might better use PropertiesAccessor<TestElement, ElementSchema>
- * However, it would require callers to pass element.getProps() which might allocate.
- * @param testElement element to get the state from
- */
- public fun updateUi(testElement: TestElement) {
+ public override fun updateUi(testElement: TestElement) {
value = when (val value = testElement.getPropertyOrNull(propertyDescriptor)) {
is BooleanProperty, null -> Value.of(value?.booleanValue ?: propertyDescriptor.defaultValue ?: false)
// TODO: should we rather fail in case we detect an unknown property?
diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/JCheckBoxBinding.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/JCheckBoxBinding.kt
new file mode 100644
index 0000000..a5744f6
--- /dev/null
+++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/JCheckBoxBinding.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.gui
+
+import org.apache.jmeter.testelement.TestElement
+import org.apache.jmeter.testelement.schema.BooleanPropertyDescriptor
+import org.apache.jmeter.testelement.schema.PropertyDescriptor
+import org.apiguardian.api.API
+import javax.swing.JCheckBox
+
+/**
+ * Binds a [JCheckBox] to a [PropertyDescriptor], so JMeter can automatically update the test element
+ * from the UI state and vice versa.
+ * @since 5.6.3
+ */
+@API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+public class JCheckBoxBinding(
+ private val checkbox: JCheckBox,
+ private val propertyDescriptor: BooleanPropertyDescriptor<*>,
+) : Binding {
+ override fun updateElement(testElement: TestElement) {
+ testElement[propertyDescriptor] = checkbox.isSelected.takeIf { it }
+ }
+
+ override fun updateUi(testElement: TestElement) {
+ checkbox.isSelected = testElement[propertyDescriptor]
+ }
+}
diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/JLabeledFieldBinding.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/JLabeledFieldBinding.kt
new file mode 100644
index 0000000..cdf5e83
--- /dev/null
+++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/JLabeledFieldBinding.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.gui
+
+import org.apache.jmeter.testelement.TestElement
+import org.apache.jmeter.testelement.schema.PropertyDescriptor
+import org.apache.jorphan.gui.JLabeledField
+import org.apiguardian.api.API
+
+/**
+ * Binds a [JLabeledField] to a [PropertyDescriptor], so JMeter can automatically update the test element
+ * from the UI state and vice versa.
+ * @since 5.6.3
+ */
+@API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+public class JLabeledFieldBinding(
+ private val labeledField: JLabeledField,
+ private val propertyDescriptor: PropertyDescriptor<*, *>,
+) : Binding {
+ override fun updateElement(testElement: TestElement) {
+ testElement[propertyDescriptor] = labeledField.text.takeIf { it.isNotEmpty() }
+ }
+
+ override fun updateUi(testElement: TestElement) {
+ labeledField.text =
+ if (testElement.getPropertyOrNull(propertyDescriptor) == null) {
+ ""
+ } else {
+ testElement.getString(propertyDescriptor)
+ }
+ }
+}
diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/JSyntaxTextAreaBinding.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/JSyntaxTextAreaBinding.kt
new file mode 100644
index 0000000..5f6b970
--- /dev/null
+++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/JSyntaxTextAreaBinding.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.gui
+
+import org.apache.jmeter.gui.util.JSyntaxTextArea
+import org.apache.jmeter.testelement.TestElement
+import org.apache.jmeter.testelement.schema.PropertyDescriptor
+import org.apiguardian.api.API
+
+/**
+ * Binds a [JSyntaxTextArea] to a [PropertyDescriptor], so JMeter can automatically update the test element
+ * from the UI state and vice versa.
+ * @since 5.6.3
+ */
+@API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+public class JSyntaxTextAreaBinding(
+ private val syntaxTextArea: JSyntaxTextArea,
+ private val propertyDescriptor: PropertyDescriptor<*, *>,
+) : Binding {
+ override fun updateElement(testElement: TestElement) {
+ testElement[propertyDescriptor] = syntaxTextArea.text.takeIf { it.isNotEmpty() }
+ }
+
+ override fun updateUi(testElement: TestElement) {
+ syntaxTextArea.setInitialText(
+ if (testElement.getPropertyOrNull(propertyDescriptor) == null) {
+ ""
+ } else {
+ testElement.getString(propertyDescriptor)
+ }
+ )
+ syntaxTextArea.caretPosition = 0
+ }
+}
diff --git a/src/core/src/main/kotlin/org/apache/jmeter/gui/JTextComponentBinding.kt b/src/core/src/main/kotlin/org/apache/jmeter/gui/JTextComponentBinding.kt
new file mode 100644
index 0000000..69bd883
--- /dev/null
+++ b/src/core/src/main/kotlin/org/apache/jmeter/gui/JTextComponentBinding.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.gui
+
+import org.apache.jmeter.testelement.TestElement
+import org.apache.jmeter.testelement.schema.PropertyDescriptor
+import org.apiguardian.api.API
+import javax.swing.JPasswordField
+import javax.swing.text.JTextComponent
+
+/**
+ * Binds a [JTextComponent] to a [PropertyDescriptor], so JMeter can automatically update the test element
+ * from the UI state and vice versa.
+ * @since 5.6.3
+ */
+@API(status = API.Status.EXPERIMENTAL, since = "5.6.3")
+public class JTextComponentBinding(
+ private val textComponent: JTextComponent,
+ private val propertyDescriptor: PropertyDescriptor<*, *>,
+) : Binding {
+ override fun updateElement(testElement: TestElement) {
+ val text = when (val component = textComponent) {
+ is JPasswordField -> String(component.password)
+ else -> component.text
+ }
+ testElement[propertyDescriptor] = text.takeIf { it.isNotEmpty() }
+ }
+
+ override fun updateUi(testElement: TestElement) {
+ textComponent.text =
+ if (testElement.getPropertyOrNull(propertyDescriptor) == null) {
+ ""
+ } else {
+ testElement.getString(propertyDescriptor)
+ }
+ }
+}
diff --git a/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt
index 4ca3c5f..ea825cf 100644
--- a/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt
+++ b/src/jorphan/src/main/kotlin/org/apache/jorphan/gui/JEditableCheckBox.kt
@@ -20,6 +20,8 @@
import org.apiguardian.api.API
import java.awt.Container
import java.awt.FlowLayout
+import java.awt.event.ActionEvent
+import javax.swing.AbstractAction
import javax.swing.Box
import javax.swing.JCheckBox
import javax.swing.JComboBox
@@ -41,6 +43,7 @@
public companion object {
public const val CHECKBOX_CARD: String = "checkbox"
public const val EDITABLE_CARD: String = "editable"
+ public const val VALUE_PROPERTY: String = "value"
}
/**
@@ -92,14 +95,21 @@
private val cards = CardLayoutWithSizeOfCurrentVisibleElement()
+ private val useExpressionAction = object : AbstractAction(configuration.startEditing) {
+ override fun actionPerformed(e: ActionEvent?) {
+ cards.next(this@JEditableCheckBox)
+ comboBox.requestFocusInWindow()
+ fireValueChanged()
+ }
+ }
+
private val checkbox: JCheckBox = JCheckBox(label).apply {
+ val cb = this
componentPopupMenu = JPopupMenu().apply {
- add(configuration.startEditing).apply {
- addActionListener {
- cards.next(this@JEditableCheckBox)
- comboBox.requestFocusInWindow()
- }
- }
+ add(useExpressionAction)
+ }
+ addItemListener {
+ fireValueChanged()
}
}
@@ -114,6 +124,7 @@
val jComboBox = it.source as JComboBox<*>
SwingUtilities.invokeLater {
if (jComboBox.isPopupVisible) {
+ fireValueChanged()
return@invokeLater
}
when (val value = jComboBox.selectedItem as String) {
@@ -121,10 +132,12 @@
checkbox.isSelected = value == configuration.trueValue
cards.show(this@JEditableCheckBox, CHECKBOX_CARD)
checkbox.requestFocusInWindow()
+ fireValueChanged()
}
}
}
}
+ // TODO: trigger value changed when the text is changed
}
private val textFieldLabel = JLabel(label).apply {
@@ -157,6 +170,23 @@
)
}
+ private var oldValue = value
+
+ override fun setEnabled(enabled: Boolean) {
+ super.setEnabled(enabled)
+ checkbox.isEnabled = enabled
+ comboBox.isEnabled = enabled
+ useExpressionAction.isEnabled = enabled
+ }
+
+ private fun fireValueChanged() {
+ val newValue = value
+ if (value != oldValue) {
+ firePropertyChange(VALUE_PROPERTY, oldValue, newValue)
+ oldValue = newValue
+ }
+ }
+
public var value: Value
get() = when (components.indexOfFirst { it.isVisible }) {
0 -> if (checkbox.isSelected) Value.Boolean.TRUE else Value.Boolean.FALSE
@@ -176,6 +206,7 @@
cards.show(this, EDITABLE_CARD)
}
}
+ fireValueChanged()
}
@get:JvmSynthetic
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/GraphQLUrlConfigGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/GraphQLUrlConfigGui.java
index 9f461e6..d729b0e 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/GraphQLUrlConfigGui.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/GraphQLUrlConfigGui.java
@@ -58,12 +58,6 @@
private static final UrlConfigDefaults URL_CONFIG_DEFAULTS = new UrlConfigDefaults();
static {
URL_CONFIG_DEFAULTS.setValidMethods(new String[] { HTTPConstants.POST, HTTPConstants.GET });
- URL_CONFIG_DEFAULTS.setDefaultMethod(HTTPConstants.POST);
- URL_CONFIG_DEFAULTS.setAutoRedirects(false);
- URL_CONFIG_DEFAULTS.setFollowRedirects(false);
- URL_CONFIG_DEFAULTS.setUseBrowserCompatibleMultipartMode(false);
- URL_CONFIG_DEFAULTS.setUseKeepAlive(true);
- URL_CONFIG_DEFAULTS.setUseMultipart(false);
URL_CONFIG_DEFAULTS.setUseMultipartVisible(false);
}
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java
index 8d8dd64..61f5b56 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/HttpDefaultsGui.java
@@ -19,10 +19,9 @@
import java.awt.BorderLayout;
import java.awt.Dimension;
-import java.awt.event.ItemEvent;
+import java.util.Arrays;
import javax.swing.BorderFactory;
-import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
@@ -34,6 +33,8 @@
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.config.gui.AbstractConfigGui;
import org.apache.jmeter.gui.GUIMenuSortOrder;
+import org.apache.jmeter.gui.JBooleanPropertyEditor;
+import org.apache.jmeter.gui.JTextComponentBinding;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.gui.util.HorizontalPanel;
import org.apache.jmeter.gui.util.VerticalPanel;
@@ -43,6 +44,7 @@
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.gui.JEditableCheckBox;
import org.apache.jorphan.gui.JFactory;
import net.miginfocom.swing.MigLayout;
@@ -57,10 +59,16 @@
private static final long serialVersionUID = 242L;
private UrlConfigGui urlConfigGui;
- private JCheckBox retrieveEmbeddedResources;
- private JCheckBox concurrentDwn;
+ private final JBooleanPropertyEditor retrieveEmbeddedResources = new JBooleanPropertyEditor(
+ HTTPSamplerBaseSchema.INSTANCE.getRetrieveEmbeddedResources(),
+ JMeterUtils.getResString("web_testing_retrieve_images"));
+ private final JBooleanPropertyEditor concurrentDwn = new JBooleanPropertyEditor(
+ HTTPSamplerBaseSchema.INSTANCE.getConcurrentDownload(),
+ JMeterUtils.getResString("web_testing_concurrent_download"));
private JTextField concurrentPool;
- private JCheckBox useMD5;
+ private final JBooleanPropertyEditor useMD5 = new JBooleanPropertyEditor(
+ HTTPSamplerBaseSchema.INSTANCE.getStoreAsMD5(),
+ JMeterUtils.getResString("response_save_as_md5")); // $NON-NLS-1$
private JTextField embeddedAllowRE; // regular expression used to match against embedded resource URLs to allow
private JTextField embeddedExcludeRE; // regular expression used to match against embedded resource URLs to discard
private JTextField sourceIpAddr; // does not apply to Java implementation
@@ -77,6 +85,27 @@
public HttpDefaultsGui() {
super();
init();
+ HTTPSamplerBaseSchema schema = HTTPSamplerBaseSchema.INSTANCE;
+ bindingGroup.addAll(
+ Arrays.asList(
+ retrieveEmbeddedResources,
+ concurrentDwn,
+ new JTextComponentBinding(concurrentPool, schema.getConcurrentDownloadPoolSize()),
+ useMD5,
+ new JTextComponentBinding(embeddedAllowRE, schema.getEmbeddedUrlAllowRegex()),
+ new JTextComponentBinding(embeddedExcludeRE, schema.getEmbeddedUrlExcludeRegex()),
+ new JTextComponentBinding(sourceIpAddr, schema.getIpSource()),
+ // TODO: sourceIpType
+ new JTextComponentBinding(proxyScheme, schema.getProxy().getScheme()),
+ new JTextComponentBinding(proxyHost, schema.getProxy().getHost()),
+ new JTextComponentBinding(proxyPort, schema.getProxy().getPort()),
+ new JTextComponentBinding(proxyUser, schema.getProxy().getUsername()),
+ new JTextComponentBinding(proxyPass, schema.getProxy().getPassword()),
+ // TODO: httpImplementation
+ new JTextComponentBinding(connectTimeOut, schema.getConnectTimeout()),
+ new JTextComponentBinding(responseTimeOut, schema.getResponseTimeout())
+ )
+ );
}
@Override
@@ -95,80 +124,39 @@
}
/**
- * Treat unset checkbox as empty, so "unset" checkboxes do not override values in HTTP Request Samplers.
- */
- private static Boolean nullIfUnset(JCheckBox checkBox) {
- return checkBox.isSelected() ? true : null;
- }
-
- /**
* Modifies a given TestElement to mirror the data in the gui components.
*
* @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement)
*/
@Override
public void modifyTestElement(TestElement config) {
- ConfigTestElement cfg = (ConfigTestElement) config;
- ConfigTestElement el = (ConfigTestElement) urlConfigGui.createTestElement();
- cfg.clear();
- cfg.addConfigElement(el);
- super.configureTestElement(config);
- HTTPSamplerBaseSchema.INSTANCE httpSchema = HTTPSamplerBaseSchema.INSTANCE;
- config.set(httpSchema.getRetrieveEmbeddedResources(), nullIfUnset(retrieveEmbeddedResources));
- enableConcurrentDwn(retrieveEmbeddedResources.isSelected());
- if (concurrentDwn.isSelected()) {
- config.set(httpSchema.getConcurrentDownload(), true);
- config.set(httpSchema.getConcurrentDownloadPoolSize(), concurrentPool.getText());
- } else {
- config.removeProperty(httpSchema.getConcurrentDownload());
+ super.modifyTestElement(config);
+ urlConfigGui.modifyTestElement(config);
+ enableConcurrentDwn();
+
+ HTTPSamplerBaseSchema httpSchema = HTTPSamplerBaseSchema.INSTANCE;
+ if (concurrentDwn.getValue().equals(JEditableCheckBox.Value.of(false))) {
+ // Even though we remove "concurrent download pool size" if the checkbox was unchecked,
+ // we do it on purpose otherwise "merging defaults to regular http sampler" would unexpectedly
+ // override "concurrent download pool size" value
+ // TODO: keep "concurrent download pool size" in defaults, however somehow remove it before merging config
+ // to the http sampler
config.removeProperty(httpSchema.getConcurrentDownloadPoolSize());
}
- config.set(httpSchema.getStoreAsMD5(), nullIfUnset(useMD5));
- config.set(httpSchema.getEmbeddedUrlAllowRegex(), embeddedAllowRE.getText());
- config.set(httpSchema.getEmbeddedUrlExcludeRegex(), embeddedExcludeRE.getText());
if(!StringUtils.isEmpty(sourceIpAddr.getText())) {
- config.set(httpSchema.getIpSource(), sourceIpAddr.getText());
config.set(httpSchema.getIpSourceType(), sourceIpType.getSelectedIndex());
} else {
- config.removeProperty(httpSchema.getIpSource());
config.removeProperty(httpSchema.getIpSourceType());
}
- config.set(httpSchema.getProxy().getScheme(), proxyScheme.getText());
- config.set(httpSchema.getProxy().getHost(), proxyHost.getText());
- config.set(httpSchema.getProxy().getPort(), proxyPort.getText());
- config.set(httpSchema.getProxy().getUsername(), proxyUser.getText());
- config.set(httpSchema.getProxy().getPassword(), String.valueOf(proxyPass.getPassword()));
config.set(httpSchema.getImplementation(), String.valueOf(httpImplementation.getSelectedItem()));
- config.set(httpSchema.getConnectTimeout(), connectTimeOut.getText());
- config.set(httpSchema.getResponseTimeout(), responseTimeOut.getText());
}
- /**
- * Implements JMeterGUIComponent.clearGui
- */
@Override
public void clearGui() {
super.clearGui();
- retrieveEmbeddedResources.setSelected(false);
- concurrentDwn.setSelected(false);
- concurrentPool.setText(String.valueOf(HTTPSamplerBase.CONCURRENT_POOL_SIZE));
- enableConcurrentDwn(false);
- useMD5.setSelected(false);
urlConfigGui.clear();
- embeddedAllowRE.setText(""); // $NON-NLS-1$
- embeddedExcludeRE.setText(""); // $NON-NLS-1$
- sourceIpAddr.setText(""); // $NON-NLS-1$
- sourceIpType.setSelectedIndex(HTTPSamplerBase.SourceType.HOSTNAME.ordinal()); //default: IP/Hostname
- proxyScheme.setText(""); // $NON-NLS-1$
- proxyHost.setText(""); // $NON-NLS-1$
- proxyPort.setText(""); // $NON-NLS-1$
- proxyUser.setText(""); // $NON-NLS-1$
- proxyPass.setText(""); // $NON-NLS-1$
- httpImplementation.setSelectedItem(""); // $NON-NLS-1$
- connectTimeOut.setText(""); // $NON-NLS-1$
- responseTimeOut.setText(""); // $NON-NLS-1$
}
@Override
@@ -176,24 +164,10 @@
super.configure(el);
AbstractTestElement samplerBase = (AbstractTestElement) el;
urlConfigGui.configure(el);
- HTTPSamplerBaseSchema httpSchema = HTTPSamplerBaseSchema.INSTANCE;
- retrieveEmbeddedResources.setSelected(samplerBase.get(httpSchema.getRetrieveEmbeddedResources()));
- concurrentDwn.setSelected(samplerBase.get(httpSchema.getConcurrentDownload()));
- concurrentPool.setText(samplerBase.getPropertyAsString(httpSchema.getConcurrentDownloadPoolSize().getName(), ""));
- useMD5.setSelected(samplerBase.get(httpSchema.getStoreAsMD5()));
- embeddedAllowRE.setText(samplerBase.get(httpSchema.getEmbeddedUrlAllowRegex()));
- embeddedExcludeRE.setText(samplerBase.get(httpSchema.getEmbeddedUrlExcludeRegex()));
- sourceIpAddr.setText(samplerBase.get(httpSchema.getIpSource()));
- sourceIpType.setSelectedIndex(samplerBase.get(httpSchema.getIpSourceType()));
- proxyScheme.setText(samplerBase.getString(httpSchema.getProxy().getScheme()));
- proxyHost.setText(samplerBase.getString(httpSchema.getProxy().getHost()));
- proxyPort.setText(samplerBase.getString(httpSchema.getProxy().getPort()));
- proxyUser.setText(samplerBase.getString(httpSchema.getProxy().getUsername()));
- proxyPass.setText(samplerBase.getString(httpSchema.getProxy().getPassword()));
+ HTTPSamplerBaseSchema httpSchema = HTTPSamplerBaseSchema.INSTANCE;
+ sourceIpType.setSelectedIndex(samplerBase.get(httpSchema.getIpSourceType()));
httpImplementation.setSelectedItem(samplerBase.getString(httpSchema.getImplementation()));
- connectTimeOut.setText(samplerBase.getString(httpSchema.getConnectTimeout()));
- responseTimeOut.setText(samplerBase.getString(httpSchema.getResponseTimeout()));
}
private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
@@ -268,18 +242,14 @@
protected JPanel createEmbeddedRsrcPanel() {
// retrieve Embedded resources
- retrieveEmbeddedResources = new JCheckBox(JMeterUtils.getResString("web_testing_retrieve_images")); // $NON-NLS-1$
// add a listener to activate or not concurrent dwn.
- retrieveEmbeddedResources.addItemListener(e -> {
- if (e.getStateChange() == ItemEvent.SELECTED) { enableConcurrentDwn(true); }
- else { enableConcurrentDwn(false); }
- });
+ retrieveEmbeddedResources.addPropertyChangeListener(
+ JEditableCheckBox.VALUE_PROPERTY,
+ ev -> enableConcurrentDwn());
// Download concurrent resources
- concurrentDwn = new JCheckBox(JMeterUtils.getResString("web_testing_concurrent_download")); // $NON-NLS-1$
- concurrentDwn.addItemListener(e -> {
- if (retrieveEmbeddedResources.isSelected() && e.getStateChange() == ItemEvent.SELECTED) { concurrentPool.setEnabled(true); }
- else { concurrentPool.setEnabled(false); }
- });
+ concurrentDwn.addPropertyChangeListener(
+ JEditableCheckBox.VALUE_PROPERTY,
+ ev -> enableConcurrentDwn());
concurrentPool = new JTextField(2); // 2 columns size
concurrentPool.setMinimumSize(new Dimension(10, (int) concurrentPool.getPreferredSize().getHeight()));
concurrentPool.setMaximumSize(new Dimension(60, (int) concurrentPool.getPreferredSize().getHeight()));
@@ -327,12 +297,7 @@
final JPanel checkBoxPanel = new VerticalPanel();
checkBoxPanel.setBorder(BorderFactory.createTitledBorder(
JMeterUtils.getResString("optional_tasks"))); // $NON-NLS-1$
-
- // Use MD5
- useMD5 = new JCheckBox(JMeterUtils.getResString("response_save_as_md5")); // $NON-NLS-1$
-
checkBoxPanel.add(useMD5);
-
return checkBoxPanel;
}
@@ -341,11 +306,13 @@
return getMinimumSize();
}
- private void enableConcurrentDwn(final boolean enable) {
+ private void enableConcurrentDwn() {
+ boolean enable = !JEditableCheckBox.Value.of(false).equals(retrieveEmbeddedResources.getValue());
concurrentDwn.setEnabled(enable);
embeddedAllowRE.setEnabled(enable);
embeddedExcludeRE.setEnabled(enable);
- concurrentPool.setEnabled(concurrentDwn.isSelected() && enable);
+ // Allow editing the pool size if "download concurrently" checkbox is set or has expression
+ concurrentPool.setEnabled(enable && !concurrentDwn.getValue().equals(JEditableCheckBox.Value.of(false)));
}
/**
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigDefaults.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigDefaults.java
index a864fa4..bf704e9 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigDefaults.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigDefaults.java
@@ -118,6 +118,7 @@
* Return the default HTTP method to be selected in the {@link UrlConfigGui}.
* @return the default HTTP method to be selected in the {@link UrlConfigGui}
*/
+ @Deprecated
public String getDefaultMethod() {
return defaultMethod;
}
@@ -126,6 +127,7 @@
* Set the default HTTP method to be selected in the {@link UrlConfigGui}.
* @param defaultMethod the default HTTP method to be selected in the {@link UrlConfigGui}
*/
+ @Deprecated
public void setDefaultMethod(String defaultMethod) {
this.defaultMethod = defaultMethod;
}
@@ -133,6 +135,7 @@
/**
* @return the default value to be set for the followRedirect checkbox in the {@link UrlConfigGui}.
*/
+ @Deprecated
public boolean isFollowRedirects() {
return followRedirects;
}
@@ -141,6 +144,7 @@
* Set the default value to be set for the followRedirect checkbox in the {@link UrlConfigGui}.
* @param followRedirects flag whether redirects should be followed
*/
+ @Deprecated
public void setFollowRedirects(boolean followRedirects) {
this.followRedirects = followRedirects;
}
@@ -148,6 +152,7 @@
/**
* @return the default value to be set for the autoRedirects checkbox in the {@link UrlConfigGui}.
*/
+ @Deprecated
public boolean isAutoRedirects() {
return autoRedirects;
}
@@ -156,6 +161,7 @@
* Set the default value to be set for the autoRedirects checkbox in the {@link UrlConfigGui}.
* @param autoRedirects flag whether redirects should be followed automatically
*/
+ @Deprecated
public void setAutoRedirects(boolean autoRedirects) {
this.autoRedirects = autoRedirects;
}
@@ -163,6 +169,7 @@
/**
* @return the default value to be set for the useKeepAlive checkbox in the {@link UrlConfigGui}.
*/
+ @Deprecated
public boolean isUseKeepAlive() {
return useKeepAlive;
}
@@ -171,6 +178,7 @@
* Set the default value to be set for the useKeepAlive checkbox in the {@link UrlConfigGui}.
* @param useKeepAlive flag whether to use keep-alive on HTTP requests
*/
+ @Deprecated
public void setUseKeepAlive(boolean useKeepAlive) {
this.useKeepAlive = useKeepAlive;
}
@@ -178,6 +186,7 @@
/**
* @return the default value to be set for the useMultipart checkbox in the {@link UrlConfigGui}.
*/
+ @Deprecated
public boolean isUseMultipart() {
return useMultipart;
}
@@ -186,6 +195,7 @@
* Set the default value to be set for the useMultipart checkbox in the {@link UrlConfigGui}.
* @param useMultipart flag whether request data should use multi-part feature
*/
+ @Deprecated
public void setUseMultipart(boolean useMultipart) {
this.useMultipart = useMultipart;
}
@@ -193,6 +203,7 @@
/**
* @return the default value to be set for the useBrowserCompatibleMultipartMode checkbox in the {@link UrlConfigGui}.
*/
+ @Deprecated
public boolean isUseBrowserCompatibleMultipartMode() {
return useBrowserCompatibleMultipartMode;
}
@@ -201,6 +212,7 @@
* Set the default value to be set for the useBrowserCompatibleMultipartMode checkbox in the {@link UrlConfigGui}.
* @param useBrowserCompatibleMultipartMode flag whether to use browser compatible multi-part mode
*/
+ @Deprecated
public void setUseBrowserCompatibleMultipartMode(boolean useBrowserCompatibleMultipartMode) {
this.useBrowserCompatibleMultipartMode = useBrowserCompatibleMultipartMode;
}
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java
index 3906262..e1c09a6 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/config/gui/UrlConfigGui.java
@@ -20,6 +20,7 @@
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
+import java.util.Arrays;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
@@ -32,7 +33,10 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.ConfigTestElement;
+import org.apache.jmeter.gui.BindingGroup;
import org.apache.jmeter.gui.JBooleanPropertyEditor;
+import org.apache.jmeter.gui.JCheckBoxBinding;
+import org.apache.jmeter.gui.JLabeledFieldBinding;
import org.apache.jmeter.gui.util.HorizontalPanel;
import org.apache.jmeter.gui.util.JSyntaxTextArea;
import org.apache.jmeter.gui.util.JTextScrollPane;
@@ -110,6 +114,8 @@
private final boolean showRawBodyPane;
private final boolean showFileUploadPane;
+ private final BindingGroup bindingGroup;
+
/**
* Constructor which is setup to show HTTP implementation, raw body pane and
* sampler fields.
@@ -150,21 +156,31 @@
this.showRawBodyPane = showRawBodyPane;
this.showFileUploadPane = showFileUploadPane;
init();
+ HTTPSamplerBaseSchema schema = HTTPSamplerBaseSchema.INSTANCE;
+ bindingGroup = new BindingGroup(
+ Arrays.asList(
+ new JLabeledFieldBinding(domain, schema.getDomain()),
+ new JLabeledFieldBinding(port, schema.getPort()),
+ new JLabeledFieldBinding(protocol, schema.getProtocol()),
+ new JLabeledFieldBinding(contentEncoding, schema.getContentEncoding()),
+ new JLabeledFieldBinding(path, schema.getPath())
+ )
+ );
+ if (notConfigOnly) {
+ bindingGroup.addAll(
+ Arrays.asList(
+ new JCheckBoxBinding(followRedirects, schema.getFollowRedirects()),
+ new JCheckBoxBinding(autoRedirects, schema.getAutoRedirects()),
+ new JLabeledFieldBinding(method, schema.getMethod()),
+ useKeepAlive,
+ useMultipart,
+ useBrowserCompatibleMultipartMode
+ )
+ );
+ }
}
public void clear() {
- domain.setText(""); // $NON-NLS-1$
- if (notConfigOnly){
- followRedirects.setSelected(getUrlConfigDefaults().isFollowRedirects());
- autoRedirects.setSelected(getUrlConfigDefaults().isAutoRedirects());
- method.setText(getUrlConfigDefaults().getDefaultMethod());
- useKeepAlive.setBooleanValue(getUrlConfigDefaults().isUseKeepAlive());
- useBrowserCompatibleMultipartMode.setBooleanValue(getUrlConfigDefaults().isUseBrowserCompatibleMultipartMode());
- }
- path.setText(""); // $NON-NLS-1$
- port.setText(""); // $NON-NLS-1$
- protocol.setText(""); // $NON-NLS-1$
- contentEncoding.setText(""); // $NON-NLS-1$
argsPanel.clear();
if(showFileUploadPane) {
filesPanel.clear();
@@ -191,6 +207,7 @@
* @param element {@link TestElement} to modify
*/
public void modifyTestElement(TestElement element) {
+ bindingGroup.updateElement(element);
boolean useRaw = showRawBodyPane && !postBodyContent.getText().isEmpty();
Arguments args;
if(useRaw) {
@@ -207,30 +224,21 @@
arg.setAlwaysEncoded(false);
args.addArgument(arg);
} else {
- args = (Arguments) argsPanel.createTestElement();
+ args = argsPanel.createTestElement();
HTTPArgument.convertArgumentsToHTTP(args);
}
if(showFileUploadPane) {
filesPanel.modifyTestElement(element);
}
- HTTPSamplerBaseSchema.INSTANCE httpSchema = HTTPSamplerBaseSchema.INSTANCE;
+ HTTPSamplerBaseSchema httpSchema = HTTPSamplerBaseSchema.INSTANCE;
// Treat "unset" checkbox as "property removal" for HTTP Request Defaults component
// Regular sampler should save both true and false values
element.set(httpSchema.getPostBodyRaw(), useRaw ? Boolean.TRUE : (notConfigOnly ? false : null));
element.set(httpSchema.getArguments(), args);
- element.set(httpSchema.getDomain(), domain.getText());
- element.set(httpSchema.getPort(), port.getText());
- element.set(httpSchema.getProtocol(), protocol.getText());
- element.set(httpSchema.getContentEncoding(), contentEncoding.getText());
- element.set(httpSchema.getPath(), path.getText());
- if (notConfigOnly){
- element.set(httpSchema.getMethod(), method.getText());
- element.set(httpSchema.getFollowRedirects(), followRedirects.isSelected());
- element.set(httpSchema.getAutoRedirects(), autoRedirects.isSelected());
- useKeepAlive.updateElement(element);
- useMultipart.updateElement(element);
- useBrowserCompatibleMultipartMode.updateElement(element);
- }
+ }
+
+ public void assignDefaultValues(TestElement element) {
+ ((HTTPSamplerBase) element).setArguments(argsPanel.createTestElement());
}
// Just append all the parameter values, and use that as the post body
@@ -270,7 +278,8 @@
*/
public void configure(TestElement el) {
setName(el.getName());
- HTTPSamplerBaseSchema.INSTANCE httpSchema = HTTPSamplerBaseSchema.INSTANCE;
+ bindingGroup.updateUi(el);
+ HTTPSamplerBaseSchema httpSchema = HTTPSamplerBaseSchema.INSTANCE;
Arguments arguments = el.get(httpSchema.getArguments());
if (showRawBodyPane) {
@@ -291,31 +300,6 @@
if(showFileUploadPane) {
filesPanel.configure(el);
}
-
- domain.setText(el.getString(httpSchema.getDomain()));
-
- String portString = el.getString(httpSchema.getPort());
-
- // Only display the port number if it is meaningfully specified
- if (portString.equals(HTTPSamplerBase.UNSPECIFIED_PORT_AS_STRING)) {
- port.setText(""); // $NON-NLS-1$
- } else {
- port.setText(portString);
- }
- // We explicitly
- String protocol = el.getPropertyAsString(httpSchema.getProtocol().getName(), "");
- this.protocol.setText(protocol);
- String encoding = el.getPropertyAsString(httpSchema.getContentEncoding().getName(), "");
- contentEncoding.setText(encoding);
- path.setText(el.getString(httpSchema.getPath()));
- if (notConfigOnly){
- method.setText(el.getString(httpSchema.getMethod()));
- followRedirects.setSelected(el.get(httpSchema.getFollowRedirects()));
- autoRedirects.setSelected(el.get(httpSchema.getAutoRedirects()));
- useKeepAlive.updateUi(el);
- useMultipart.updateUi(el);
- useBrowserCompatibleMultipartMode.updateUi(el);
- }
}
private void init() {// called from ctor, so must not be overridable
@@ -384,35 +368,30 @@
if (notConfigOnly){
followRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects")); // $NON-NLS-1$
JFactory.small(followRedirects);
- followRedirects.setSelected(getUrlConfigDefaults().isFollowRedirects());
followRedirects.addChangeListener(this);
followRedirects.setVisible(getUrlConfigDefaults().isFollowRedirectsVisible());
autoRedirects = new JCheckBox(JMeterUtils.getResString("follow_redirects_auto")); //$NON-NLS-1$
JFactory.small(autoRedirects);
autoRedirects.addChangeListener(this);
- autoRedirects.setSelected(getUrlConfigDefaults().isAutoRedirects());// Default changed in 2.3 and again in 2.4
autoRedirects.setVisible(getUrlConfigDefaults().isAutoRedirectsVisible());
useKeepAlive = new JBooleanPropertyEditor(
HTTPSamplerBaseSchema.INSTANCE.getUseKeepalive(),
JMeterUtils.getResString("use_keepalive"));
JFactory.small(useKeepAlive);
- useKeepAlive.setBooleanValue(getUrlConfigDefaults().isUseKeepAlive());
useKeepAlive.setVisible(getUrlConfigDefaults().isUseKeepAliveVisible());
useMultipart = new JBooleanPropertyEditor(
HTTPSamplerBaseSchema.INSTANCE.getUseMultipartPost(),
JMeterUtils.getResString("use_multipart_for_http_post")); // $NON-NLS-1$
JFactory.small(useMultipart);
- useMultipart.setBooleanValue(getUrlConfigDefaults().isUseMultipart());
useMultipart.setVisible(getUrlConfigDefaults().isUseMultipartVisible());
useBrowserCompatibleMultipartMode = new JBooleanPropertyEditor(
HTTPSamplerBaseSchema.INSTANCE.getUseBrowserCompatibleMultipart(),
JMeterUtils.getResString("use_multipart_mode_browser")); // $NON-NLS-1$
JFactory.small(useBrowserCompatibleMultipartMode);
- useBrowserCompatibleMultipartMode.setBooleanValue(getUrlConfigDefaults().isUseBrowserCompatibleMultipartMode());
useBrowserCompatibleMultipartMode.setVisible(getUrlConfigDefaults().isUseBrowserCompatibleMultipartModeVisible());
}
@@ -464,7 +443,7 @@
* @return a new {@link Arguments} instance associated with the specific GUI used in this component
*/
protected Arguments createHTTPArgumentsTestElement() {
- return (Arguments) argsPanel.createTestElement();
+ return argsPanel.createTestElement();
}
class ValidationTabbedPane extends AbstractValidationTabbedPane {
@@ -507,7 +486,7 @@
* @return false if one argument has a name
*/
private boolean canSwitchToRawBodyPane() {
- Arguments arguments = (Arguments) argsPanel.createTestElement();
+ Arguments arguments = argsPanel.createTestElement();
for (int i = 0; i < arguments.getArgumentCount(); i++) {
if(!StringUtils.isEmpty(arguments.getArgument(i).getName())) {
return false;
@@ -542,7 +521,7 @@
*/
void convertParametersToRaw() {
if (showRawBodyPane && postBodyContent.getText().isEmpty()) {
- postBodyContent.setInitialText(computePostBody((Arguments)argsPanel.createTestElement()));
+ postBodyContent.setInitialText(computePostBody(argsPanel.createTestElement()));
postBodyContent.setCaretPosition(0);
}
}
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/GraphQLHTTPSamplerGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/GraphQLHTTPSamplerGui.java
index c393e77..d8e2f83 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/GraphQLHTTPSamplerGui.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/GraphQLHTTPSamplerGui.java
@@ -22,6 +22,9 @@
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.protocol.http.config.gui.GraphQLUrlConfigGui;
import org.apache.jmeter.protocol.http.config.gui.UrlConfigGui;
+import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseSchema;
+import org.apache.jmeter.protocol.http.util.HTTPConstants;
+import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.util.JMeterUtils;
/**
@@ -33,6 +36,15 @@
private static final long serialVersionUID = 1L;
+ @Override
+ public void assignDefaultValues(TestElement element) {
+ super.assignDefaultValues(element);
+ HTTPSamplerBaseSchema schema = HTTPSamplerBaseSchema.INSTANCE;
+ element.set(schema.getMethod(), HTTPConstants.POST);
+ element.set(schema.getUseBrowserCompatibleMultipart(), false);
+ element.set(schema.getUseMultipartPost(), false);
+ }
+
public GraphQLHTTPSamplerGui() {
super();
}
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java
index 3459a73..ca7234f 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/gui/HttpTestSampleGui.java
@@ -19,10 +19,9 @@
import java.awt.BorderLayout;
import java.awt.Dimension;
-import java.awt.event.ItemEvent;
+import java.util.Arrays;
import javax.swing.BorderFactory;
-import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
@@ -31,7 +30,10 @@
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
+import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.gui.GUIMenuSortOrder;
+import org.apache.jmeter.gui.JBooleanPropertyEditor;
+import org.apache.jmeter.gui.JTextComponentBinding;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.gui.util.HorizontalPanel;
import org.apache.jmeter.gui.util.VerticalPanel;
@@ -40,9 +42,11 @@
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseSchema;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
+import org.apache.jmeter.protocol.http.util.HTTPConstants;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.gui.JEditableCheckBox;
import org.apache.jorphan.gui.JFactory;
import net.miginfocom.swing.MigLayout;
@@ -57,10 +61,16 @@
private static final long serialVersionUID = 242L;
private UrlConfigGui urlConfigGui;
- private JCheckBox retrieveEmbeddedResources;
- private JCheckBox concurrentDwn;
+ private final JBooleanPropertyEditor retrieveEmbeddedResources = new JBooleanPropertyEditor(
+ HTTPSamplerBaseSchema.INSTANCE.getRetrieveEmbeddedResources(),
+ JMeterUtils.getResString("web_testing_retrieve_images"));
+ private final JBooleanPropertyEditor concurrentDwn = new JBooleanPropertyEditor(
+ HTTPSamplerBaseSchema.INSTANCE.getConcurrentDownload(),
+ JMeterUtils.getResString("web_testing_concurrent_download"));
private JTextField concurrentPool;
- private JCheckBox useMD5;
+ private final JBooleanPropertyEditor useMD5 = new JBooleanPropertyEditor(
+ HTTPSamplerBaseSchema.INSTANCE.getStoreAsMD5(),
+ JMeterUtils.getResString("response_save_as_md5")); // $NON-NLS-1$
private JTextField embeddedAllowRE; // regular expression used to match against embedded resource URLs to allow
private JTextField embeddedExcludeRE; // regular expression used to match against embedded resource URLs to exclude
private JTextField sourceIpAddr; // does not apply to Java implementation
@@ -77,14 +87,40 @@
private final boolean isAJP;
public HttpTestSampleGui() {
- isAJP = false;
- init();
+ this(false);
}
// For use by AJP
protected HttpTestSampleGui(boolean ajp) {
isAJP = ajp;
init();
+ HTTPSamplerBaseSchema schema = HTTPSamplerBaseSchema.INSTANCE;
+ bindingGroup.addAll(
+ Arrays.asList(
+ retrieveEmbeddedResources,
+ concurrentDwn,
+ new JTextComponentBinding(concurrentPool, schema.getConcurrentDownloadPoolSize()),
+ useMD5,
+ new JTextComponentBinding(embeddedAllowRE, schema.getEmbeddedUrlAllowRegex()),
+ new JTextComponentBinding(embeddedExcludeRE, schema.getEmbeddedUrlExcludeRegex())
+ )
+ );
+ if (!isAJP) {
+ bindingGroup.addAll(
+ Arrays.asList(
+ new JTextComponentBinding(sourceIpAddr, schema.getIpSource()),
+ // TODO: sourceIpType
+ new JTextComponentBinding(proxyScheme, schema.getProxy().getScheme()),
+ new JTextComponentBinding(proxyHost, schema.getProxy().getHost()),
+ new JTextComponentBinding(proxyPort, schema.getProxy().getPort()),
+ new JTextComponentBinding(proxyUser, schema.getProxy().getUsername()),
+ new JTextComponentBinding(proxyPass, schema.getProxy().getPassword()),
+ // TODO: httpImplementation
+ new JTextComponentBinding(connectTimeOut, schema.getConnectTimeout()),
+ new JTextComponentBinding(responseTimeOut, schema.getResponseTimeout())
+ )
+ );
+ }
}
/**
@@ -96,24 +132,9 @@
final HTTPSamplerBase samplerBase = (HTTPSamplerBase) element;
HTTPSamplerBaseSchema httpSchema = HTTPSamplerBaseSchema.INSTANCE;
urlConfigGui.configure(element);
- retrieveEmbeddedResources.setSelected(samplerBase.isImageParser());
- concurrentDwn.setSelected(samplerBase.isConcurrentDwn());
- concurrentPool.setText(samplerBase.getConcurrentPool());
- useMD5.setSelected(samplerBase.useMD5());
- embeddedAllowRE.setText(samplerBase.getEmbeddedUrlRE());
- embeddedExcludeRE.setText(samplerBase.getEmbededUrlExcludeRE());
if (!isAJP) {
- sourceIpAddr.setText(samplerBase.getIpSource());
sourceIpType.setSelectedIndex(samplerBase.getIpSourceType());
-
- proxyScheme.setText(samplerBase.getString(httpSchema.getProxy().getScheme()));
- proxyHost.setText(samplerBase.getString(httpSchema.getProxy().getHost()));
- proxyPort.setText(samplerBase.getString(httpSchema.getProxy().getPort()));
- proxyUser.setText(samplerBase.getString(httpSchema.getProxy().getUsername()));
- proxyPass.setText(samplerBase.getString(httpSchema.getProxy().getPassword()));
httpImplementation.setSelectedItem(samplerBase.getString(httpSchema.getImplementation()));
- connectTimeOut.setText(samplerBase.getString(httpSchema.getConnectTimeout()));
- responseTimeOut.setText(samplerBase.getString(httpSchema.getResponseTimeout()));
}
}
@@ -121,10 +142,19 @@
* {@inheritDoc}
*/
@Override
- public TestElement createTestElement() {
- HTTPSamplerBase sampler = new HTTPSamplerProxy();
- modifyTestElement(sampler);
- return sampler;
+ public TestElement makeTestElement() {
+ return new HTTPSamplerProxy();
+ }
+
+ @Override
+ public void assignDefaultValues(TestElement element) {
+ super.assignDefaultValues(element);
+ HTTPSamplerBaseSchema schema = HTTPSamplerBaseSchema.INSTANCE;
+ // It probably does not make much sense overriding HTTP method with HTTP Request Defaults, so we set it here
+ element.set(schema.getMethod(), HTTPConstants.GET);
+ element.set(schema.getFollowRedirects(), true);
+ element.set(schema.getUseKeepalive(), true);
+ urlConfigGui.assignDefaultValues(element);
}
/**
@@ -134,31 +164,19 @@
*/
@Override
public void modifyTestElement(TestElement sampler) {
- sampler.clear();
+ super.modifyTestElement(sampler);
urlConfigGui.modifyTestElement(sampler);
final HTTPSamplerBase samplerBase = (HTTPSamplerBase) sampler;
HTTPSamplerBaseSchema httpSchema = samplerBase.getSchema();
- samplerBase.setImageParser(retrieveEmbeddedResources.isSelected());
- enableConcurrentDwn(retrieveEmbeddedResources.isSelected());
- samplerBase.setConcurrentDwn(concurrentDwn.isSelected());
- samplerBase.setConcurrentPool(concurrentPool.getText());
- samplerBase.setMD5(useMD5.isSelected());
- samplerBase.setEmbeddedUrlRE(embeddedAllowRE.getText());
- samplerBase.setEmbeddedUrlExcludeRE(embeddedExcludeRE.getText());
+ enableConcurrentDwn();
if (!isAJP) {
- samplerBase.setIpSource(sourceIpAddr.getText());
- samplerBase.setIpSourceType(sourceIpType.getSelectedIndex());
-
- samplerBase.set(httpSchema.getProxy().getScheme(), proxyScheme.getText());
- samplerBase.set(httpSchema.getProxy().getHost(), proxyHost.getText());
- samplerBase.set(httpSchema.getProxy().getPort(), proxyPort.getText());
- samplerBase.set(httpSchema.getProxy().getUsername(), proxyUser.getText());
- samplerBase.set(httpSchema.getProxy().getPassword(), String.valueOf(proxyPass.getPassword()));
+ if (!StringUtils.isEmpty(sourceIpAddr.getText())) {
+ samplerBase.set(httpSchema.getIpSourceType(), sourceIpType.getSelectedIndex());
+ } else {
+ samplerBase.removeProperty(httpSchema.getIpSourceType());
+ }
samplerBase.set(httpSchema.getImplementation(), String.valueOf(httpImplementation.getSelectedItem()));
- samplerBase.set(httpSchema.getConnectTimeout(), connectTimeOut.getText());
- samplerBase.set(httpSchema.getResponseTimeout(), responseTimeOut.getText());
}
- super.configureTestElement(sampler);
}
/**
@@ -279,18 +297,14 @@
protected JPanel createEmbeddedRsrcPanel() {
// retrieve Embedded resources
- retrieveEmbeddedResources = new JCheckBox(JMeterUtils.getResString("web_testing_retrieve_images")); // $NON-NLS-1$
// add a listener to activate or not concurrent dwn.
- retrieveEmbeddedResources.addItemListener(e -> {
- if (e.getStateChange() == ItemEvent.SELECTED) { enableConcurrentDwn(true); }
- else { enableConcurrentDwn(false); }
- });
+ retrieveEmbeddedResources.addPropertyChangeListener(
+ JEditableCheckBox.VALUE_PROPERTY,
+ ev -> enableConcurrentDwn());
// Download concurrent resources
- concurrentDwn = new JCheckBox(JMeterUtils.getResString("web_testing_concurrent_download")); // $NON-NLS-1$
- concurrentDwn.addItemListener(e -> {
- if (retrieveEmbeddedResources.isSelected() && e.getStateChange() == ItemEvent.SELECTED) { concurrentPool.setEnabled(true); }
- else { concurrentPool.setEnabled(false); }
- });
+ concurrentDwn.addPropertyChangeListener(
+ JEditableCheckBox.VALUE_PROPERTY,
+ ev -> enableConcurrentDwn());
concurrentPool = new JTextField(2); // 2 column size
concurrentPool.setMinimumSize(new Dimension(10, (int) concurrentPool.getPreferredSize().getHeight()));
concurrentPool.setMaximumSize(new Dimension(60, (int) concurrentPool.getPreferredSize().getHeight()));
@@ -341,8 +355,6 @@
checkBoxPanel.setBorder(BorderFactory.createTitledBorder(
JMeterUtils.getResString("optional_tasks"))); // $NON-NLS-1$
- // Use MD5
- useMD5 = new JCheckBox(JMeterUtils.getResString("response_save_as_md5")); // $NON-NLS-1$
checkBoxPanel.add(useMD5);
return checkBoxPanel;
@@ -370,38 +382,19 @@
return getMinimumSize();
}
- /**
- * {@inheritDoc}
- */
@Override
public void clearGui() {
super.clearGui();
- retrieveEmbeddedResources.setSelected(false);
- concurrentDwn.setSelected(false);
- concurrentPool.setText(String.valueOf(HTTPSamplerBase.CONCURRENT_POOL_SIZE));
- enableConcurrentDwn(false);
- useMD5.setSelected(false);
urlConfigGui.clear();
- embeddedAllowRE.setText(""); // $NON-NLS-1$
- if (!isAJP) {
- sourceIpAddr.setText(""); // $NON-NLS-1$
- sourceIpType.setSelectedIndex(HTTPSamplerBase.SourceType.HOSTNAME.ordinal()); //default: IP/Hostname
- proxyScheme.setText(""); // $NON-NLS-1$
- proxyHost.setText(""); // $NON-NLS-1$
- proxyPort.setText(""); // $NON-NLS-1$
- proxyUser.setText(""); // $NON-NLS-1$
- proxyPass.setText(""); // $NON-NLS-1$
- httpImplementation.setSelectedItem(""); // $NON-NLS-1$
- connectTimeOut.setText(""); // $NON-NLS-1$
- responseTimeOut.setText(""); // $NON-NLS-1$
- }
}
- private void enableConcurrentDwn(boolean enable) {
+ private void enableConcurrentDwn() {
+ boolean enable = !JEditableCheckBox.Value.of(false).equals(retrieveEmbeddedResources.getValue());
concurrentDwn.setEnabled(enable);
embeddedAllowRE.setEnabled(enable);
embeddedExcludeRE.setEnabled(enable);
- concurrentPool.setEnabled(concurrentDwn.isSelected() && enable);
+ // Allow editing the pool size if "download concurrently" checkbox is set or has expression
+ concurrentPool.setEnabled(enable && !concurrentDwn.getValue().equals(JEditableCheckBox.Value.of(false)));
}
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java
index f087afe..1f4af2b 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/HTTPArgumentsPanel.java
@@ -104,10 +104,10 @@
}
@Override
- public TestElement createTestElement() {
+ public Arguments createTestElement() {
Arguments args = getUnclonedParameters();
- super.configureTestElement(args);
- return (TestElement) args.clone();
+ assignDefaultValues(args);
+ return (Arguments) args.clone();
}
/**
diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
index 12db65f..2818fc8 100644
--- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
+++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
@@ -315,11 +315,12 @@
if (StringUtils.isNotEmpty(url.getQuery())) {
path += "?" + url.getQuery();
}
+ // setMethod must be before setPath as setPath uses method to determine if parameters should be parsed or not
+ httpSampler.setMethod(request.getMethod());
httpSampler.setPath(path);
httpSampler.setDomain(url.getHost());
httpSampler.setUseKeepAlive(request.isKeepAlive());
httpSampler.setFollowRedirects(true);
- httpSampler.setMethod(request.getMethod());
HeaderManager headerManager = createHeaderManager(request);
httpSampler.addTestElement(headerManager);
configureTimeout(request, httpSampler);
diff --git a/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseSchema.kt b/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseSchema.kt
index 587d9ed..5f2a317 100644
--- a/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseSchema.kt
+++ b/src/protocol/http/src/main/kotlin/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseSchema.kt
@@ -65,7 +65,7 @@
by testElement("HTTPSampler.dns_cache_manager")
public val method: StringPropertyDescriptor<HTTPSamplerBaseSchema>
- by string("HTTPSampler.method")
+ by string("HTTPSampler.method", default = HTTPSamplerBase.DEFAULT_METHOD)
public val protocol: StringPropertyDescriptor<HTTPSamplerBaseSchema>
by string("HTTPSampler.protocol", default = HTTPConstants.PROTOCOL_HTTP)
diff --git a/src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/HttpSamplerPrintDslTest.kt b/src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/HttpSamplerPrintDslTest.kt
index d934844..0888218 100644
--- a/src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/HttpSamplerPrintDslTest.kt
+++ b/src/protocol/http/src/test/kotlin/org/apache/jmeter/protocol/http/sampler/HttpSamplerPrintDslTest.kt
@@ -92,6 +92,8 @@
+element
}
+ // "arguments" property is assigned in HTTPSamplerBase constructor, so it comes before
+ // name and guiClass common properties
assertEquals(
"""
org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy::class {
@@ -103,17 +105,19 @@
it[testClass] = "org.apache.jmeter.config.Arguments"
}
}
+ it[name] = "HTTP Request"
+ it[guiClass] = "org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui"
it[method] = "GET"
it[followRedirects] = true
it[useKeepalive] = true
- it[implementation] = "HttpClient4"
- it[name] = "HTTP Request"
- it[guiClass] = "org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui"
}
}
""".trimIndent().replace("\r\n", "\n"),
- DslPrinterTraverser().also { tree.traverse(it) }.toString().replace("\r\n", "\n")
+ DslPrinterTraverser().also { tree.traverse(it) }.toString().replace("\r\n", "\n"),
+ "HTTP request created with HttpTestSampleGui.createTestElement() should have expected output shape. " +
+ "DslPrinterTraverser does not print the values which are automatically assigned in constructor, " +
+ "so the expected output does not have it[testClass] = HTTPSamplerProxy, and empty list in arguments"
)
}
@@ -130,18 +134,15 @@
it[testClass] = "org.apache.jmeter.config.Arguments"
}
}
+ it[name] = "HTTP Request"
+ it[guiClass] = "org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui"
it[method] = "GET"
it[followRedirects] = true
it[useKeepalive] = true
- it[implementation] = "HttpClient4"
- it[name] = "HTTP Request"
- it[guiClass] = "org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui"
}
}
}.keys.first() as TestElement
- createdWithUi.traverse(RemoveDefaultValues)
-
// We compare elements manually, and call assertEquals(toString, toString) so
// the test output looks better (diff in IDE) in case of the failure
// If we use just assertEquals(createdWithUi, createdWithDsl), then there will be no "diff in IDE"
diff --git a/xdocs/usermanual/jmeter_tutorial.xml b/xdocs/usermanual/jmeter_tutorial.xml
index 1f7ff28..814cf30 100644
--- a/xdocs/usermanual/jmeter_tutorial.xml
+++ b/xdocs/usermanual/jmeter_tutorial.xml
@@ -259,6 +259,10 @@
(and possibly translations as well).</li>
</ol>
</li>
+ <li>Override <code>org.apache.jmeter.gui.JMeterGUIComponent.makeTestElement</code> method so it returns
+ the appropriate <code>TestElement</code>. JMeter will use <code>makeTestElement</code> when user creates the element
+ from the UI. In most cases it should be just creating the test element like
+ <code>return new SetupThreadGroup()</code>.</li>
<li>Create your GUI. Whatever style you like, layout your GUI. Your class ultimately extends
<code>JPanel</code>, so your layout must be in your class's own <code>ContentPane</code>.
Do not hook up GUI elements to your <code>TestElement</code> class via actions and events.
@@ -287,10 +291,15 @@
</li>
</ol>
</li>
- <li>Implement <code>public void configure(TestElement el)</code>
+ <li>Then you need to wire UI elements with the properties of the new <code>TestElement</code>. If you create
+ <code>TestElementSchema</code> for your test element (see <code>ThreadGroupSchema</code>), then you could use
+ automatic wiring with <code>PropertyEditorCollection</code></li>
+ <li>If you do not use schema for wiring properties to UI control, or if you have non-trivial controls,
+ you might customize <code>TestElement</code> properties to UI control mapping by overriding <code>public void configure(TestElement el)</code>
<ol>
<li>Be sure to call <code>super.configure(e)</code>. This will populate some of the data for you, like
- the name of the element.</li>
+ the name of the element. Note: JMeter reuses UI elements when user changes the active element in test tree,
+ so you need to set all the text fields in <code>configure</code> method to avoid displaying stale contents.</li>
<li>Use this method to set data into your GUI elements. Example:
<source>
public void configure(TestElement el) {
@@ -312,16 +321,18 @@
}
</source>
</li>
- <li>Implement <code>public void modifyTestElement(TestElement e)</code>. This is where you
- move the data from your GUI elements to the <code>TestElement</code>. It is the logical reverse of the
- previous method.
+ <li>If you do not use schema for wiring UI controls to <code>TestElement</code> properties,
+ or if you want customized behavior, you might override <code>public void modifyTestElement(TestElement e)</code>.
+ It is the logical reverse of <code>configure</code> method.
<ol>
- <li>Call <code>super.configureTestElement(e)</code>. This will take care of some default data for
+ <li>Call <code>super.modifyTestElement(e)</code>. This will take care of some default data for
you.</li>
+ <li>Note: in most cases, you want to treat "empty field" as "absent property", so make sure to
+ remove the property if the input field is empty.</li>
<li>Example:
<source>
public void modifyTestElement(TestElement e) {
- super.configureTestElement(e);
+ super.modifyTestElement(e);
e.setProperty(new BooleanProperty(
RegexExtractor.USEHEADERS,
useHeaders.isSelected()));
@@ -339,16 +350,9 @@
</li>
</ol>
</li>
- <li>Implement <code>public TestElement createTestElement()</code>. This method should create a
- new instance of your <code>TestElement</code> class, and then pass it to the <code>modifyTestElement(TestElement)</code>
- method you made above
-<source>
-public TestElement createTestElement() {
- RegexExtractor extractor = new RegexExtractor();
- modifyTestElement(extractor);
- return extractor;
-}
-</source>
+ <li>If your UI includes controls that do not map to <code>TestElement</code> properties (sliders, tabs),
+ then you might want to reset them when user switches the controls. You can do that by overriding
+ <code>clearGui()</code> method and resetting the controls there.
</li>
</ol>
</li>