/* * -----------------------------------------------------------------------\ * 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.message; import org.perfcake.util.StringTemplate; import java.io.Serializable; import java.util.List; import java.util.Properties; /** * Holds a message template based on a provided sample, keeps references to configured validators and renders the message before it is actually sent. * Rendering means properties substitution in the message payload. * Logging is very minimalistic as this class is very simple and we want to maximize its performance. * * @author <a href="mailto:marvenec@gmail.com">Martin Večeřa</a> * @author <a href="mailto:pavel.macik@gmail.com">Pavel Macík</a> */ public class MessageTemplate implements Serializable { private static final long serialVersionUID = 6172258079690233417L; /** * Original message sample. */ private final Message message; /** * How many times the message should be sent in one iteration. */ private final long multiplicity; /** * The list of validator references that should be used to validate a response to this message. */ private final List<String> validatorIds; /** * True when the original message contains anything to be replaced. */ private final boolean isStringMessage; /** * A template prepared to be rendered. */ private transient StringTemplate template; /** * True when there are any templates in any part of the message including headers and properties. */ private boolean hasTemplates = false; /** * Creates a new template based on the message sample. Multiplicity and validator references can be provided as well. * * @param message * A sample message. * @param multiplicity * How many times the message should be sent in one iteration. * @param validatorIds * List of validators to validate a response. */ public MessageTemplate(final Message message, final long multiplicity, final List<String> validatorIds) { this.message = new Message(); this.message.setPayload(message.getPayload()); this.message.setHeaders(templatize(message.getHeaders())); this.message.setProperties(templatize(message.getProperties())); this.isStringMessage = message.getPayload() instanceof String; if (isStringMessage) { prepareTemplate(); } this.multiplicity = multiplicity; this.validatorIds = validatorIds; } /** * Converts string properties to templates when there are placeholders in them. * * @param input * Properties to be processed. * @return New properties with string containing properties converted to templates. */ private Properties templatize(final Properties input) { final Properties result = new Properties(); input.forEach((key, value) -> { final StringTemplate template = new StringTemplate(value.toString()); if (template.hasPlaceholders()) { if (template.hasDynamicPlaceholders()) { result.put(key, template); hasTemplates = true; } else { result.put(key, template.toString()); } } else { result.put(key, value); } }); return result; } /** * Converts template properties back to string while rendering the placeholders with specific values. * * @param input * Properties to be processed. * @param placeholders * Placeholder values to be placed in the resulting property values. * @return New properties with rendered string values. */ private Properties untemplatize(final Properties input, final Properties placeholders) { final Properties result = new Properties(); input.forEach((key, value) -> { if (value instanceof StringTemplate) { result.put(key, ((StringTemplate) value).toString(placeholders)); } else { result.put(key, value); } }); return result; } /** * Gets the original sample message. * * @return The original sample message. */ public Message getMessage() { return message; } /** * Gets a new message instance. * * @return A new message. */ private static Message newMessage() { return new Message(); } /** * Renders the message template. * * @param properties * Properties to be replaced in the message payload. * @return A new message instance with the rendered payload. */ public Message getFilteredMessage(final Properties properties) { if (isStringMessage && hasTemplates) { final Message newMessage = newMessage(); if (template != null) { newMessage.setPayload(template.toString(properties)); } else { newMessage.setPayload(message.getPayload()); } newMessage.setHeaders(untemplatize(message.getHeaders(), properties)); newMessage.setProperties(untemplatize(message.getProperties(), properties)); return newMessage; } else { final Message newMessage = newMessage(); newMessage.setPayload(message.getPayload()); newMessage.setHeaders((Properties) message.getHeaders().clone()); newMessage.setProperties((Properties) message.getProperties().clone()); return newMessage; } } private void prepareTemplate() { // find out if there are any attributes in the text message to be replaced final StringTemplate tmpTemplate = new StringTemplate((String) message.getPayload()); if (tmpTemplate.hasDynamicPlaceholders()) { this.template = tmpTemplate; hasTemplates = true; } else { // return the rendered template back, it might not have any placeholders now, but there could have been some math replacements etc. message.setPayload(tmpTemplate.toString()); } } /** * How many times the message should be sent in one iteration? * * @return The number of how many times the message should be sent in one iteration. */ public Long getMultiplicity() { return multiplicity; } /** * Gets the list of validator references that should be used to validate a response to this message. * * @return The list of validator references that should be used to validate a response to this message. */ public List<String> getValidatorIds() { return validatorIds; } // fill in the transient field private void readObject(final java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException { stream.defaultReadObject(); if (isStringMessage) { prepareTemplate(); } } }