
org.perfcake.scenario.XmlFactory Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------\
* PerfCake
*
* Copyright (C) 2010 - 2016 the original author or authors.
*
* Licensed 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.perfcake.scenario;
import org.perfcake.PerfCakeConst;
import org.perfcake.PerfCakeException;
import org.perfcake.RunInfo;
import org.perfcake.common.Period;
import org.perfcake.common.PeriodType;
import org.perfcake.message.Message;
import org.perfcake.message.MessageTemplate;
import org.perfcake.message.correlator.Correlator;
import org.perfcake.message.generator.MessageGenerator;
import org.perfcake.message.receiver.Receiver;
import org.perfcake.message.sender.MessageSenderManager;
import org.perfcake.message.sequence.Sequence;
import org.perfcake.message.sequence.SequenceManager;
import org.perfcake.model.HeaderType;
import org.perfcake.model.PropertyType;
import org.perfcake.model.Scenario.Generator;
import org.perfcake.model.Scenario.Messages;
import org.perfcake.model.Scenario.Messages.Message.ValidatorRef;
import org.perfcake.model.Scenario.Reporting;
import org.perfcake.model.Scenario.Sender;
import org.perfcake.model.Scenario.Validation;
import org.perfcake.reporting.ReportManager;
import org.perfcake.reporting.destination.Destination;
import org.perfcake.reporting.reporter.Reporter;
import org.perfcake.util.ObjectFactory;
import org.perfcake.util.Utils;
import org.perfcake.validation.MessageValidator;
import org.perfcake.validation.ValidationManager;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
/**
* Loads the scenario from an XML file.
*
* @author Pavel Macík
* @author Martin Večeřa
*/
public class XmlFactory implements ScenarioFactory {
/**
* A logger.
*/
private static final Logger log = LogManager.getLogger(XmlFactory.class);
/**
* DOM model of the scenario.
*/
private org.perfcake.model.Scenario scenarioModel;
/**
* The scenario definition loaded from the file.
*/
private String scenarioConfig;
/**
* Parsed scenario object.
*/
private Scenario scenario = null;
/**
* The factory is not part of public API.
*/
protected XmlFactory() {
}
@Override
public void init(final URL scenarioUrl) throws PerfCakeException {
try {
prepareModelTwoPass(scenarioUrl);
if (log.isDebugEnabled()) {
log.debug(String.format("Loaded scenario definition from '%s'.", scenarioUrl.toString()));
}
} catch (final IOException e) {
throw new PerfCakeException("Cannot read scenario configuration: ", e);
}
}
/**
* Parses the scenario twice, first to read the properties defined in it, second using the new properties directly
* in the scenario.
*
* @param scenarioUrl
* Scenario location URL.
* @throws PerfCakeException
* When it was not possible to parse the scenario.
* @throws IOException
* When it was not possible to read the scenario definition.
*/
private void prepareModelTwoPass(final URL scenarioUrl) throws PerfCakeException, IOException {
// two-pass parsing to first read the properties specified in the scenario and then use them
this.scenarioConfig = Utils.readFilteredContent(scenarioUrl);
this.scenarioModel = parse();
putScenarioPropertiesToSystem(parseScenarioProperties());
this.scenarioConfig = Utils.readFilteredContent(scenarioUrl);
this.scenarioModel = parse();
final Properties scenarioProperties = parseScenarioProperties();
putScenarioPropertiesToSystem(scenarioProperties);
if (log.isDebugEnabled() && scenarioProperties != null) {
log.debug("--- Scenario Properties ---");
scenarioProperties.forEach((key, value) -> log.debug("'- {}:{}", key, value));
}
}
@Override
public synchronized Scenario getScenario() throws PerfCakeException {
if (scenario == null) {
scenario = new Scenario();
final RunInfo runInfo = parseRunInfo();
final MessageGenerator messageGenerator = parseGenerator();
messageGenerator.setRunInfo(runInfo);
scenario.setGenerator(messageGenerator);
scenario.setMessageSenderManager(parseSender(messageGenerator.getThreads()));
scenario.setReceiver(parseReceiver());
scenario.setCorrelator(parseCorrelator());
scenario.setReportManager(parseReporting());
scenario.getReportManager().setRunInfo(runInfo);
final ValidationManager validationManager = parseValidation();
final List messageTemplates = parseMessages(validationManager);
scenario.setMessageStore(messageTemplates);
scenario.setValidationManager(validationManager);
scenario.setSequenceManager(parseSequences());
}
return scenario;
}
private Receiver parseReceiver() throws PerfCakeException {
if (scenarioModel.getReceiver() != null) {
try {
final org.perfcake.model.Scenario.Receiver rec = scenarioModel.getReceiver();
String receiverClass = rec.getClazz();
if (!receiverClass.contains(".")) {
receiverClass = DEFAULT_RECEIVER_PACKAGE + "." + receiverClass;
}
final int threads = Integer.parseInt(rec.getThreads());
final String source = rec.getSource();
if (log.isDebugEnabled()) {
log.debug("--- Receiver (" + receiverClass + ") ---");
log.debug(" threads=" + threads);
}
final Properties receiverProperties = getPropertiesFromList(rec.getProperty());
Utils.logProperties(log, Level.DEBUG, receiverProperties, " ");
final Receiver receiver = (Receiver) ObjectFactory.summonInstance(receiverClass, receiverProperties);
receiver.setThreads(threads);
if (source != null) {
receiver.setSource(source);
}
return receiver;
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new PerfCakeException("Cannot parse message generator configuration: ", e);
}
} else {
return null;
}
}
private Correlator parseCorrelator() throws PerfCakeException {
if (scenarioModel.getReceiver() != null && scenarioModel.getReceiver().getCorrelator() != null) {
try {
final org.perfcake.model.Scenario.Receiver.Correlator cor = scenarioModel.getReceiver().getCorrelator();
String correlatorClass = cor.getClazz();
if (!correlatorClass.contains(".")) {
correlatorClass = DEFAULT_CORRELATOR_PACKAGE + "." + correlatorClass;
}
if (log.isDebugEnabled()) {
log.debug(" '- Correlator (" + correlatorClass + ")");
}
final Properties correlatorProperties = getPropertiesFromList(cor.getProperty());
Utils.logProperties(log, Level.DEBUG, correlatorProperties, " '- ");
return (Correlator) ObjectFactory.summonInstance(correlatorClass, correlatorProperties);
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new PerfCakeException("Cannot parse message generator configuration: ", e);
}
} else {
return null;
}
}
/**
* Does the parsing itself by using JAXB.
*
* @return Parsed JAXB scenario model.
* @throws PerfCakeException
* If XML is not valid or cannot be successfully parsed.
*/
private org.perfcake.model.Scenario parse() throws PerfCakeException {
try {
final Source scenarioXml = new StreamSource(new ByteArrayInputStream(scenarioConfig.getBytes(Utils.getDefaultEncoding())));
final String schemaFileName = "perfcake-scenario-" + PerfCakeConst.XSD_SCHEMA_VERSION + ".xsd";
final URL backupUrl = new URL("http://schema.perfcake.org/" + schemaFileName);
URL scenarioXsdUrl = Utils.getResourceAsUrl("/schemas/" + schemaFileName);
try {
InputStream test = scenarioXsdUrl.openStream();
//noinspection ResultOfMethodCallIgnored
test.read(); // there always is a byte
test.close(); // we do not need finally for this as we could not have failed
} catch (IOException e) {
scenarioXsdUrl = backupUrl; // backup taken from the web
}
final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
final Schema schema = schemaFactory.newSchema(scenarioXsdUrl);
final JAXBContext context = JAXBContext.newInstance(org.perfcake.model.Scenario.class);
final Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setSchema(schema);
return (org.perfcake.model.Scenario) unmarshaller.unmarshal(scenarioXml);
} catch (final SAXException e) {
throw new PerfCakeException("Cannot validate scenario configuration. PerfCake installation seems broken. ", e);
} catch (final JAXBException e) {
throw new PerfCakeException("Cannot parse scenario configuration: ", e);
} catch (final MalformedURLException e) {
throw new PerfCakeException("Cannot read scenario schema to validate it: ", e);
} catch (final UnsupportedEncodingException e) {
throw new PerfCakeException("set encoding is not supported: ", e);
}
}
private void putScenarioPropertiesToSystem(final Properties properties) {
if (properties != null) {
properties.forEach(System.getProperties()::put);
}
}
private static Properties getPropertiesFromList(final List properties) throws PerfCakeException {
final Properties props = new Properties();
for (final PropertyType p : properties) {
final Element valueElement = p.getAny();
final String valueString = p.getValue();
if (valueElement != null && valueString != null) {
throw new PerfCakeException(String.format("A property tag can either have an attribute value (%s) or the body (%s) set, not both at the same time.", valueString, valueElement.toString()));
} else if (valueElement == null && valueString == null) {
throw new PerfCakeException("A property tag must either have an attribute value or the body set.");
}
props.put(p.getName(), valueString == null ? valueElement : valueString);
}
return props;
}
/**
* Parses {@link org.perfcake.RunInfo} from the generator configuration.
*
* @return {@link org.perfcake.RunInfo} representing the configuration.
* @throws PerfCakeException
* When there is a parse exception.
*/
protected RunInfo parseRunInfo() throws PerfCakeException {
final org.perfcake.model.Scenario.Run run = scenarioModel.getRun();
final RunInfo runInfo = new RunInfo(new Period(PeriodType.valueOf(run.getType().toUpperCase()), Long.parseLong(run.getValue())));
if (log.isDebugEnabled()) {
log.debug("--- Run Info ---");
log.debug(" " + runInfo.toString());
}
return runInfo;
}
/**
* Parses the generator
element into an {@link org.perfcake.message.generator.MessageGenerator} instance.
*
* @return A particular implementation of {@link org.perfcake.message.generator.MessageGenerator}.
* @throws org.perfcake.PerfCakeException
* When there is a parse exception.
*/
protected MessageGenerator parseGenerator() throws PerfCakeException {
final MessageGenerator generator;
try {
final Generator gen = scenarioModel.getGenerator();
String generatorClass = gen.getClazz();
if (!generatorClass.contains(".")) {
generatorClass = DEFAULT_GENERATOR_PACKAGE + "." + generatorClass;
}
final int threads = Integer.parseInt(gen.getThreads());
if (log.isDebugEnabled()) {
log.debug("--- Generator (" + generatorClass + ") ---");
log.debug(" threads=" + threads);
}
final Properties generatorProperties = getPropertiesFromList(gen.getProperty());
Utils.logProperties(log, Level.DEBUG, generatorProperties, " ");
generator = (MessageGenerator) ObjectFactory.summonInstance(generatorClass, generatorProperties);
generator.setThreads(threads);
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new PerfCakeException("Cannot parse message generator configuration: ", e);
}
return generator;
}
/**
* Parses the sequences
element into a {@link SequenceManager} instance.
*
* @return {@link SequenceManager} containing the parsed {@link Sequence Sequences}.
* @throws org.perfcake.PerfCakeException
* When there is a parse exception.
*/
protected SequenceManager parseSequences() throws PerfCakeException {
final SequenceManager sequenceManager = new SequenceManager();
try {
final org.perfcake.model.Scenario.Sequences sequences = scenarioModel.getSequences();
if (sequences != null) {
for (org.perfcake.model.Scenario.Sequences.Sequence seq : sequences.getSequence()) {
final String sequenceId = seq.getId();
String sequenceClass = seq.getClazz();
final Properties sequenceProperties = getPropertiesFromList(seq.getProperty());
if (!sequenceClass.contains(".")) {
sequenceClass = DEFAULT_SEQUENCE_PACKAGE + "." + sequenceClass;
}
if (log.isDebugEnabled()) {
log.debug("--- Sequence (" + sequenceId + ":" + sequenceClass + ") ---");
}
Utils.logProperties(log, Level.DEBUG, sequenceProperties, " ");
final Sequence sequence = (Sequence) ObjectFactory.summonInstance(sequenceClass, sequenceProperties);
sequenceManager.addSequence(sequenceId, sequence);
}
}
} catch (ReflectiveOperationException e) {
throw new PerfCakeException("Cannot parse sequences: ", e);
}
return sequenceManager;
}
/**
* Parses the sender
element into a {@link org.perfcake.message.sender.MessageSenderManager} instance.
*
* @param senderPoolSize
* Size of the message sender pool.
* @return {@link org.perfcake.message.sender.MessageSenderManager} containing the parsed {@link org.perfcake.message.sender.MessageSender}s.
* @throws org.perfcake.PerfCakeException
* When there is a parse exception.
*/
protected MessageSenderManager parseSender(final int senderPoolSize) throws PerfCakeException {
final MessageSenderManager msm;
final Sender sen = scenarioModel.getSender();
String senderClass = sen.getClazz();
if (!senderClass.contains(".")) {
senderClass = DEFAULT_SENDER_PACKAGE + "." + senderClass;
}
if (log.isDebugEnabled()) {
log.debug("--- Sender (" + senderClass + ") ---");
}
final Properties senderProperties = getPropertiesFromList(sen.getProperty());
Utils.logProperties(log, Level.DEBUG, senderProperties, " ");
msm = new MessageSenderManager();
msm.setSenderClass(senderClass);
msm.setSenderPoolSize(senderPoolSize);
if (sen.getTarget() != null) {
msm.setMessageSenderProperty("target", sen.getTarget());
}
for (final Entry
© 2015 - 2025 Weber Informatics LLC | Privacy Policy