/*
* 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.threads;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.apache.jmeter.assertions.Assertion;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.control.Controller;
import org.apache.jmeter.control.TransactionController;
import org.apache.jmeter.control.TransactionSampler;
import org.apache.jmeter.engine.event.LoopIterationListener;
import org.apache.jmeter.engine.util.ConfigMergabilityIndicator;
import org.apache.jmeter.engine.util.NoConfigMerge;
import org.apache.jmeter.processor.PostProcessor;
import org.apache.jmeter.processor.PreProcessor;
import org.apache.jmeter.samplers.SampleListener;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testbeans.TestBeanHelper;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.timers.Timer;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.HashTreeTraverser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* HashTreeTraverser implementation that traverses the Test Tree to build:
* <ul>
* <li>A map with key Sampler and as value the associated SamplePackage</li>
* <li>A map with key TransactionController and as value the associated SamplePackage</li>
* </ul>
*/
public class TestCompiler implements HashTreeTraverser {
private static final Logger log = LoggerFactory.getLogger(TestCompiler.class);
/**
* This set keeps track of which ObjectPairs have been seen.
* It seems to be used to prevent adding a child to a parent if the child has already been added.
* If the ObjectPair (child, parent) is present, then the child has been added.
* Otherwise, the child is added to the parent and the pair is added to the Set.
*/
private static final Set<ObjectPair> PAIRING = new HashSet<>();
private final LinkedList<TestElement> stack = new LinkedList<>();
private final Map<Sampler, SamplePackage> samplerConfigMap = new HashMap<>();
private final Map<TransactionController, SamplePackage> transactionControllerConfigMap =
new HashMap<>();
private final HashTree testTree;
public TestCompiler(HashTree testTree) {
this.testTree = testTree;
}
/**
* Clears the pairing Set Called by StandardJmeterEngine at the start of a
* test run.
*/
public static void initialize() {
// synch is probably not needed as only called before run starts
synchronized (PAIRING) {
PAIRING.clear();
}
}
/**
* Configures sampler from SamplePackage extracted from Test plan and returns it
* @param sampler {@link Sampler}
* @return {@link SamplePackage}
*/
public SamplePackage configureSampler(Sampler sampler) {
SamplePackage pack = samplerConfigMap.get(sampler);
pack.setSampler(sampler);
configureWithConfigElements(sampler, pack.getConfigs());
return pack;
}
/**
* Configures Transaction Sampler from SamplePackage extracted from Test plan and returns it
* @param transactionSampler {@link TransactionSampler}
* @return {@link SamplePackage}
*/
public SamplePackage configureTransactionSampler(TransactionSampler transactionSampler) {
TransactionController controller = transactionSampler.getTransactionController();
SamplePackage pack = transactionControllerConfigMap.get(controller);
pack.setSampler(transactionSampler);
return pack;
}
/**
* Reset pack to its initial state
* @param pack the {@link SamplePackage} to reset
*/
public void done(SamplePackage pack) {
pack.recoverRunningVersion();
}
/** {@inheritDoc} */
@Override
public void addNode(Object node, HashTree subTree) {
stack.addLast((TestElement) node);
}
/** {@inheritDoc} */
@Override
public void subtractNode() {
if (log.isDebugEnabled()) {
log.debug("Subtracting node, stack size = {}", stack.size());
}
TestElement child = stack.getLast();
trackIterationListeners(stack);
if (child instanceof Sampler) {
saveSamplerConfigs((Sampler) child);
}
else if(child instanceof TransactionController) {
saveTransactionControllerConfigs((TransactionController) child);
}
stack.removeLast();
if (!stack.isEmpty()) {
TestElement parent = stack.getLast();
boolean duplicate = false;
// Bug 53750: this condition used to be in ObjectPair#addTestElements()
if (parent instanceof Controller && (child instanceof Sampler || child instanceof Controller)) {
if (parent instanceof TestCompilerHelper) {
TestCompilerHelper te = (TestCompilerHelper) parent;
duplicate = !te.addTestElementOnce(child);
} else { // this is only possible for 3rd party controllers by default
ObjectPair pair = new ObjectPair(child, parent);
synchronized (PAIRING) {// Called from multiple threads
if (!PAIRING.contains(pair)) {
parent.addTestElement(child);
PAIRING.add(pair);
} else {
duplicate = true;
}
}
}
}
if (duplicate) {
if (log.isWarnEnabled()) {
log.warn("Unexpected duplicate for {} and {}", parent.getClass(), child.getClass());
}
}
}
}
private void trackIterationListeners(LinkedList<TestElement> pStack) {
TestElement child = pStack.getLast();
if (child instanceof LoopIterationListener) {
ListIterator<TestElement> iter = pStack.listIterator(pStack.size());
while (iter.hasPrevious()) {
TestElement item = iter.previous();
if (item == child) {
continue;
}
if (item instanceof Controller) {
TestBeanHelper.prepare(child);
((Controller) item).addIterationListener((LoopIterationListener) child);
break;
}
}
}
}
/** {@inheritDoc} */
@Override
public void processPath() {
}
private void saveSamplerConfigs(Sampler sam) {
List<ConfigTestElement> configs = new LinkedList<>();
List<Controller> controllers = new LinkedList<>();
List<SampleListener> listeners = new LinkedList<>();
List<Timer> timers = new LinkedList<>();
List<Assertion> assertions = new LinkedList<>();
LinkedList<PostProcessor> posts = new LinkedList<>();
LinkedList<PreProcessor> pres = new LinkedList<>();
for (int i = stack.size(); i > 0; i--) {
addDirectParentControllers(controllers, stack.get(i - 1));
List<PreProcessor> tempPre = new LinkedList<>();
List<PostProcessor> tempPost = new LinkedList<>();
for (Object item : testTree.list(stack.subList(0, i))) {
if (item instanceof ConfigTestElement) {
configs.add((ConfigTestElement) item);
}
if (item instanceof SampleListener) {
listeners.add((SampleListener) item);
}
if (item instanceof Timer) {
timers.add((Timer) item);
}
if (item instanceof Assertion) {
assertions.add((Assertion) item);
}
if (item instanceof PostProcessor) {
tempPost.add((PostProcessor) item);
}
if (item instanceof PreProcessor) {
tempPre.add((PreProcessor) item);
}
}
pres.addAll(0, tempPre);
posts.addAll(0, tempPost);
}
SamplePackage pack = new SamplePackage(configs, listeners, timers, assertions,
posts, pres, controllers);
pack.setSampler(sam);
pack.setRunningVersion(true);
samplerConfigMap.put(sam, pack);
}
private void saveTransactionControllerConfigs(TransactionController tc) {
List<ConfigTestElement> configs = new LinkedList<>();
List<Controller> controllers = new LinkedList<>();
List<SampleListener> listeners = new LinkedList<>();
List<Timer> timers = new LinkedList<>();
List<Assertion> assertions = new LinkedList<>();
LinkedList<PostProcessor> posts = new LinkedList<>();
LinkedList<PreProcessor> pres = new LinkedList<>();
for (int i = stack.size(); i > 0; i--) {
addDirectParentControllers(controllers, stack.get(i - 1));
for (Object item : testTree.list(stack.subList(0, i))) {
if (item instanceof SampleListener) {
listeners.add((SampleListener) item);
}
if (item instanceof Assertion) {
assertions.add((Assertion) item);
}
}
}
SamplePackage pack = new SamplePackage(configs, listeners, timers, assertions,
posts, pres, controllers);
pack.setSampler(new TransactionSampler(tc, tc.getName()));
pack.setRunningVersion(true);
transactionControllerConfigMap.put(tc, pack);
}
/**
* @param controllers
* @param maybeController
*/
private void addDirectParentControllers(List<Controller> controllers, TestElement maybeController) {
if (maybeController instanceof Controller) {
log.debug("adding controller: {} to sampler config", maybeController);
controllers.add((Controller) maybeController);
}
}
private static class ObjectPair
{
private final TestElement child;
private final TestElement parent;
public ObjectPair(TestElement child, TestElement parent) {
this.child = child;
this.parent = parent;
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return child.hashCode() + parent.hashCode();
}
/** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (o instanceof ObjectPair) {
return child == ((ObjectPair) o).child && parent == ((ObjectPair) o).parent;
}
return false;
}
}
private void configureWithConfigElements(Sampler sam, List<ConfigTestElement> configs) {
sam.clearTestElementChildren();
for (ConfigTestElement config : configs) {
if (!(config instanceof NoConfigMerge))
{
if(sam instanceof ConfigMergabilityIndicator) {
if(((ConfigMergabilityIndicator)sam).applies(config)) {
sam.addTestElement(config);
}
} else {
// Backward compatibility
sam.addTestElement(config);
}
}
}
}
}