99import org .apache .jmeter .config .ConfigTestElement ;
1010import org .apache .jmeter .protocol .http .config .gui .HttpDefaultsGui ;
1111import org .apache .jmeter .protocol .http .sampler .HTTPSamplerBase ;
12+ import org .apache .jmeter .protocol .http .sampler .HTTPSamplerProxy ;
1213import org .apache .jmeter .testelement .TestElement ;
1314import org .apache .jmeter .testelement .property .TestElementProperty ;
15+ import org .apache .jorphan .collections .HashTree ;
1416import us .abstracta .jmeter .javadsl .codegeneration .MethodCall ;
1517import us .abstracta .jmeter .javadsl .codegeneration .MethodCallContext ;
1618import us .abstracta .jmeter .javadsl .codegeneration .MethodCallContext .MethodCallContextEndListener ;
1719import us .abstracta .jmeter .javadsl .codegeneration .MethodParam ;
1820import us .abstracta .jmeter .javadsl .codegeneration .SingleGuiClassCallBuilder ;
1921import us .abstracta .jmeter .javadsl .codegeneration .TestElementParamBuilder ;
22+ import us .abstracta .jmeter .javadsl .codegeneration .params .BoolParam ;
2023import us .abstracta .jmeter .javadsl .codegeneration .params .EncodingParam ;
24+ import us .abstracta .jmeter .javadsl .core .BuildTreeContext ;
25+ import us .abstracta .jmeter .javadsl .core .BuildTreeContext .TreeContextEndListener ;
2126import us .abstracta .jmeter .javadsl .core .configs .BaseConfigElement ;
2227import us .abstracta .jmeter .javadsl .http .DslBaseHttpSampler .BaseHttpSamplerCodeBuilder ;
2328import us .abstracta .jmeter .javadsl .http .DslHttpSampler .HttpClientImpl ;
@@ -48,6 +53,7 @@ public class DslHttpDefaults extends BaseConfigElement {
4853 protected String proxyUser ;
4954 protected String proxyPassword ;
5055 protected HttpClientImpl clientImpl ;
56+ protected Boolean followRedirects ;
5157
5258 public DslHttpDefaults () {
5359 super ("HTTP Request Defaults" , HttpDefaultsGui .class );
@@ -169,6 +175,20 @@ public DslHttpDefaults encoding(Charset encoding) {
169175 return this ;
170176 }
171177
178+ /**
179+ * Specifies if by default HTTP redirects should be automatically followed (a new request
180+ * automatically created) when detected, or not.
181+ *
182+ * @param enable specifies whether to enable or disable automatic redirections by defaults. When
183+ * not set then the default is true.
184+ * @return the config element for further configuration or usage.
185+ * @since 1.9
186+ */
187+ public DslHttpDefaults followRedirects (boolean enable ) {
188+ this .followRedirects = enable ;
189+ return this ;
190+ }
191+
172192 /**
173193 * Allows enabling automatic download of HTML embedded resources (images, iframes, etc) by
174194 * default.
@@ -339,7 +359,8 @@ public DslHttpDefaults clientImpl(HttpClientImpl clientImpl) {
339359 * <p>
340360 * When using reset connection for each thread consider tuning OS like explained in "Configure
341361 * your environment" section of
342- * <a href="https://medium.com/@chientranthien/how-to-generate-high-load-benchmark-with-jmeter-80e828a67592">this article</a>.
362+ * <a href="https://medium.com/@chientranthien/how-to-generate-high-load-benchmark-with-jmeter-80e828a67592">this
363+ * article</a>.
343364 * <p>
344365 * <b>Warning:</b> This setting is applied at JVM level, which means that it will affect the
345366 * entire test plan and potentially other test plans running in the same JVM instance.
@@ -403,6 +424,62 @@ protected TestElement buildTestElement() {
403424 return ret ;
404425 }
405426
427+ @ Override
428+ public HashTree buildTreeUnder (HashTree parent , BuildTreeContext context ) {
429+ HashTree ret = super .buildTreeUnder (parent , context );
430+ if (followRedirects != null ) {
431+ buildEndListener (context .getParent ()).followRedirects = followRedirects ;
432+ }
433+ return ret ;
434+ }
435+
436+ private static DefaultsTreeContextEndListener buildEndListener (BuildTreeContext parentCtx ) {
437+ return parentCtx .getOrCreateEntry (DslHttpDefaults .class .getName (),
438+ () -> new DefaultsTreeContextEndListener (parentCtx ));
439+ }
440+
441+ /* Set as protected to avoid this method to appear to users while creating a test plan,
442+ but still be visible for DslHttpSampler */
443+ protected static void addPendingFollowRedirectsElement (HTTPSamplerProxy element ,
444+ BuildTreeContext context ) {
445+ buildEndListener (context .getRoot ()).pendingFollowRedirectsElements .add (element );
446+ }
447+
448+ private static class DefaultsTreeContextEndListener implements TreeContextEndListener {
449+
450+ private Boolean followRedirects ;
451+ private final List <HTTPSamplerProxy > pendingFollowRedirectsElements = new ArrayList <>();
452+
453+ private DefaultsTreeContextEndListener (BuildTreeContext context ) {
454+ context .addEndListener (this );
455+ }
456+
457+ @ Override
458+ public void execute (BuildTreeContext context , HashTree tree ) {
459+ if (followRedirects != null ) {
460+ setChildrenToFollowRedirects (tree );
461+ } else {
462+ pendingFollowRedirectsElements .stream ()
463+ .filter (e -> e .getPropertyAsString (HTTPSamplerBase .FOLLOW_REDIRECTS ).isEmpty ())
464+ .forEach (e -> e .setFollowRedirects (true ));
465+ }
466+ }
467+
468+ private void setChildrenToFollowRedirects (HashTree tree ) {
469+ tree .forEach ((key , value ) -> {
470+ if (key instanceof HTTPSamplerProxy ) {
471+ HTTPSamplerProxy sampler = (HTTPSamplerProxy ) key ;
472+ if (sampler .getPropertyAsString (HTTPSamplerBase .FOLLOW_REDIRECTS ).isEmpty ()) {
473+ sampler .setFollowRedirects (followRedirects );
474+ }
475+ } else {
476+ setChildrenToFollowRedirects (value );
477+ }
478+ });
479+ }
480+
481+ }
482+
406483 public static class CodeBuilder extends SingleGuiClassCallBuilder {
407484
408485 public CodeBuilder (List <Method > builderMethods ) {
@@ -437,95 +514,144 @@ protected MethodCall buildMethodCall(MethodCallContext context) {
437514
438515 public void registerDependency (MethodCallContext context ) {
439516 MethodCallContext parentCtx = context .getParent ();
440- BuildContextEndListener endListener = parentCtx .computeEntryIfAbsent (DslHttpDefaults .class ,
441- () -> new BuildContextEndListener (parentCtx ));
442- String encoding = context .getTestElement ()
443- .getPropertyAsString (HTTPSamplerBase .CONTENT_ENCODING );
444- boolean isDefaultCandidate = context .getTestElement ()
445- .getPropertyAsString (TestElement .GUI_CLASS )
517+ DefaultsMethodContextEndListener endListener = parentCtx .computeEntryIfAbsent (
518+ DslHttpDefaults .class , () -> new DefaultsMethodContextEndListener (parentCtx ));
519+ TestElement testElement = context .getTestElement ();
520+ String encoding = testElement .getPropertyAsString (HTTPSamplerBase .CONTENT_ENCODING );
521+ boolean isDefaultCandidate = testElement .getPropertyAsString (TestElement .GUI_CLASS )
446522 .equals (HttpDefaultsGui .class .getName ());
447523 endListener .registerEncoding (encoding , context , isDefaultCandidate );
524+ boolean followRedirects =
525+ isDefaultCandidate || testElement .getPropertyAsBoolean (HTTPSamplerBase .FOLLOW_REDIRECTS );
526+ endListener .registerFollowRedirect (followRedirects , context , isDefaultCandidate );
448527 }
449528
450- private static class BuildContextEndListener implements MethodCallContextEndListener {
529+ private static class DefaultsMethodContextEndListener implements MethodCallContextEndListener {
451530
452- private final List <EncodedCall > calls = new ArrayList <>();
531+ private final List <EncodedCall > encodedCalls = new ArrayList <>();
532+ private final List <RedirectableCall > redirectableCalls = new ArrayList <>();
453533
454- private BuildContextEndListener (MethodCallContext parentCtx ) {
534+ private DefaultsMethodContextEndListener (MethodCallContext parentCtx ) {
455535 parentCtx .addEndListener (this );
456536 }
457537
458538 public void registerEncoding (String encoding , MethodCallContext ret ,
459539 boolean isDefaultCandidate ) {
460- calls .add (new EncodedCall (encoding , ret , isDefaultCandidate ));
540+ encodedCalls .add (new EncodedCall (encoding , ret , isDefaultCandidate ));
541+ }
542+
543+ public void registerFollowRedirect (boolean followRedirects , MethodCallContext ret ,
544+ boolean isDefaultCandidate ) {
545+ redirectableCalls .add (new RedirectableCall (followRedirects , ret , isDefaultCandidate ));
461546 }
462547
463548 @ Override
464549 public void execute (MethodCallContext ctx , MethodCall ret ) {
465- final EncodedCall defaultsCall = findDefaultsCall (ctx );
550+ solveDefaultEncoding (ctx );
551+ solveDefaultFollowRedirects (ctx );
552+ }
553+
554+ private void solveDefaultEncoding (MethodCallContext ctx ) {
555+ EncodedCall defaultsCall = findDefaultEncodingCall (ctx );
466556 if (defaultsCall != null ) {
467- calls .stream ()
557+ encodedCalls .stream ()
468558 .filter (c -> c != defaultsCall && !c .isDefaultCandidate
469559 && c .encoding .equals (defaultsCall .encoding ))
470560 .forEach (EncodedCall ::removeEncoding );
471561 }
472- MethodCallContext parentCtx = ctx .getParent ();
473- if (parentCtx != null ) {
474- BuildContextEndListener parentListener = parentCtx .computeEntryIfAbsent (
475- DslHttpDefaults .class , () -> new BuildContextEndListener (parentCtx ));
562+ DefaultsMethodContextEndListener parentListener = buildParentContextEndListener (ctx );
563+ if (parentListener != null ) {
476564 parentListener .registerEncoding (defaultsCall != null ? defaultsCall .encoding : "" ,
477565 defaultsCall != null ? defaultsCall .ctx : null , false );
478566 }
479567 }
480568
481- private EncodedCall findDefaultsCall (MethodCallContext ctx ) {
482- if (calls .size () == 1 ) {
483- return calls .get (0 );
569+ private static DefaultsMethodContextEndListener buildParentContextEndListener (
570+ MethodCallContext ctx ) {
571+ MethodCallContext parentCtx = ctx .getParent ();
572+ return (parentCtx != null ) ? parentCtx .computeEntryIfAbsent (DslHttpDefaults .class ,
573+ () -> new DefaultsMethodContextEndListener (parentCtx )) : null ;
574+ }
575+
576+ private EncodedCall findDefaultEncodingCall (MethodCallContext ctx ) {
577+ if (encodedCalls .size () == 1 ) {
578+ return encodedCalls .get (0 );
484579 }
485- EncodedCall defaultsCall = calls .stream ()
580+ EncodedCall ret = encodedCalls .stream ()
486581 .filter (c -> c .isDefaultCandidate && !c .encoding .isEmpty ())
487582 .findAny ()
488583 .orElse (null );
489- if (defaultsCall != null ) {
490- return defaultsCall ;
584+ if (ret != null ) {
585+ return ret ;
491586 }
492- String someEncoding = calls .stream ()
587+ String someEncoding = encodedCalls .stream ()
493588 .filter (c -> !c .encoding .isEmpty ())
494589 .map (c -> c .encoding )
495590 .findAny ()
496591 .orElse (null );
497- if (someEncoding != null && calls .stream ()
592+ if (someEncoding != null && encodedCalls .stream ()
498593 .allMatch (c -> c .isDefaultCandidate || c .encoding .equals (someEncoding ))) {
499- defaultsCall = calls .stream ()
594+ ret = encodedCalls .stream ()
500595 .filter (c -> c .isDefaultCandidate )
501596 .findAny ()
502597 .orElse (null );
503- if (defaultsCall == null ) {
504- defaultsCall = buildDefaultsCall (ctx );
598+ if (ret == null ) {
599+ ret = new EncodedCall ( "" , buildDefaultsCall (ctx ), true );
505600 }
506- defaultsCall .setEncoding (someEncoding );
507- return defaultsCall ;
601+ ret .setEncoding (someEncoding );
602+ return ret ;
508603 }
509604 return null ;
510605 }
511606
512- private EncodedCall buildDefaultsCall (MethodCallContext ctx ) {
513- MethodCallContext child = ctx .prependChild (
514- new DslHttpDefaults ().buildConfiguredTestElement (), null );
515- return new EncodedCall ("" , child , true );
607+ private MethodCallContext buildDefaultsCall (MethodCallContext ctx ) {
608+ return ctx .prependChild (new DslHttpDefaults ().buildConfiguredTestElement (), null );
609+ }
610+
611+ private void solveDefaultFollowRedirects (MethodCallContext ctx ) {
612+ RedirectableCall defaultsCall = findDefaultRedirectableCall (ctx );
613+ if (defaultsCall != null ) {
614+ redirectableCalls .stream ()
615+ .filter (c -> c != defaultsCall )
616+ .forEach (RedirectableCall ::removeFollowRedirects );
617+ }
618+ DefaultsMethodContextEndListener parentListener = buildParentContextEndListener (ctx );
619+ if (parentListener != null ) {
620+ parentListener .registerFollowRedirect (
621+ defaultsCall == null || defaultsCall .followRedirects ,
622+ defaultsCall != null ? defaultsCall .ctx : null , false );
623+ }
624+ }
625+
626+ private RedirectableCall findDefaultRedirectableCall (MethodCallContext ctx ) {
627+ if (redirectableCalls .size () == 1 ) {
628+ return redirectableCalls .get (0 );
629+ }
630+ if (redirectableCalls .stream ().allMatch (c -> c .isDefaultCandidate || !c .followRedirects )) {
631+ RedirectableCall ret = redirectableCalls .stream ()
632+ .filter (c -> c .isDefaultCandidate )
633+ .findAny ()
634+ .orElse (null );
635+ if (ret == null ) {
636+ ret = new RedirectableCall (false , buildDefaultsCall (ctx ), true );
637+ }
638+ ret .setFollowRedirects (false );
639+ return ret ;
640+ }
641+ return null ;
516642 }
517643
518644 }
519645
520646 }
521647
522- public static class EncodedCall {
648+ private static class EncodedCall {
523649
524650 private String encoding ;
525651 private final MethodCallContext ctx ;
526652 private final boolean isDefaultCandidate ;
527653
528- public EncodedCall (String encoding , MethodCallContext ctx , boolean isDefaultCandidate ) {
654+ private EncodedCall (String encoding , MethodCallContext ctx , boolean isDefaultCandidate ) {
529655 this .encoding = encoding ;
530656 this .ctx = ctx ;
531657 this .isDefaultCandidate = isDefaultCandidate ;
@@ -537,12 +663,41 @@ public void setEncoding(String encoding) {
537663 }
538664
539665 public void removeEncoding () {
540- MethodCall methodCall = ctx .getMethodCall ();
541- methodCall .unchain ("encoding" );
542- if (methodCall .getReturnType () == DslHttpDefaults .class && methodCall .chainSize () == 0 ) {
543- ctx .getParent ().getMethodCall ()
544- .replaceChild (methodCall , MethodCall .emptyCall ());
545- }
666+ ctx .getMethodCall ().unchain ("encoding" );
667+ removeEmptyDefaultsCall (ctx );
668+ }
669+
670+ }
671+
672+ private static void removeEmptyDefaultsCall (MethodCallContext ctx ) {
673+ MethodCall methodCall = ctx .getMethodCall ();
674+ if (methodCall .getReturnType () == DslHttpDefaults .class && methodCall .chainSize () == 0 ) {
675+ ctx .getParent ().getMethodCall ()
676+ .replaceChild (methodCall , MethodCall .emptyCall ());
677+ }
678+ }
679+
680+ private static class RedirectableCall {
681+
682+ private boolean followRedirects ;
683+ private final MethodCallContext ctx ;
684+ private final boolean isDefaultCandidate ;
685+
686+ private RedirectableCall (boolean followRedirects , MethodCallContext ctx ,
687+ boolean isDefaultCandidate ) {
688+ this .followRedirects = followRedirects ;
689+ this .ctx = ctx ;
690+ this .isDefaultCandidate = isDefaultCandidate ;
691+ }
692+
693+ public void setFollowRedirects (boolean followRedirects ) {
694+ this .followRedirects = followRedirects ;
695+ ctx .getMethodCall ().chain ("followRedirects" , new BoolParam (followRedirects , true ));
696+ }
697+
698+ public void removeFollowRedirects () {
699+ ctx .getMethodCall ().unchain ("followRedirects" );
700+ removeEmptyDefaultsCall (ctx );
546701 }
547702
548703 }
0 commit comments