/* * -----------------------------------------------------------------------\ * 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.sender; import org.perfcake.PerfCakeException; import org.perfcake.message.Message; import org.perfcake.reporting.MeasurementUnit; import org.perfcake.util.Utils; import java.io.BufferedOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.stream.Collectors; /** * Invokes external command (specified by {@link #target} property) * in a separate process to send the message payload (if message is specified) passed to the standard input of * the process or as the command argument. * * @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 CommandSender extends AbstractSender { /** * Reference to a process where the command is executed. */ private Process process; /** * The writer that is used to pass the message payload to the command's process standard input stream. */ private PrintWriter writer; /** * The message payload that is passed to the command to send it. */ private String messagePayload; /** * The reader that is used to read the response from the command's process standard output stream. */ private InputStreamReader reader; /** * Specifies from where the message to send is taken. */ private MessageFrom messageFrom = MessageFrom.STDIN; /** * The prefix for the command. */ private String commandPrefix = ""; /** * The actual command that is executed. */ private String command = ""; /** * The array of environment variables passed to the command. */ private String[] environmentVariables; /** * The origin where the messages are taken from.. * * @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 static enum MessageFrom { STDIN, ARGUMENTS } @Override public void doInit(final Properties messageAttributes) throws PerfCakeException { // nop } @Override public void doClose() { // nop } @Override public void preSend(final Message message, final Properties messageAttributes) throws Exception { super.preSend(message, messageAttributes); if (message != null) { final Serializable payload = message.getPayload(); if (payload != null) { messagePayload = payload.toString(); } else { messagePayload = null; } } else { messagePayload = null; } if (messagePayload != null && messageFrom == MessageFrom.ARGUMENTS) { command = (commandPrefix + " " + safeGetTarget(messageAttributes) + " " + messagePayload).trim(); } else { command = (commandPrefix + " " + safeGetTarget(messageAttributes)).trim(); } final List<String> variables = new ArrayList<>(); if (messageAttributes != null) { messageAttributes.stringPropertyNames().forEach(prop -> variables.add(prop + "=" + messageAttributes.getProperty(prop))); } variables.addAll(System.getenv().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.toList())); if (message != null) { variables.addAll(message.getHeaders().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.toList())); variables.addAll(message.getProperties().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.toList())); } environmentVariables = variables.toArray(new String[variables.size()]); } @Override public Serializable doSend(final Message message, final MeasurementUnit measurementUnit) throws Exception { process = Runtime.getRuntime().exec(command, environmentVariables); if (messagePayload != null && messageFrom == MessageFrom.STDIN) { writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(process.getOutputStream()), Utils.getDefaultEncoding()), true); writer.write(messagePayload); writer.flush(); writer.close(); } process.waitFor(); final char[] cbuf = new char[10 * 1024]; this.reader = new InputStreamReader(process.getInputStream(), Utils.getDefaultEncoding()); // note that Content-Length is available at this point final StringBuilder sb = new StringBuilder(); int ch = reader.read(cbuf); while (ch != -1) { sb.append(cbuf, 0, ch); ch = reader.read(cbuf); } return sb.toString(); } @Override public void postSend(final Message message) throws Exception { super.postSend(message); reader.close(); process.getInputStream().close(); } /** * Gets the value of messageFrom property. * * @return The messageFrom value. */ public MessageFrom getMessageFrom() { return messageFrom; } /** * Sets the value of messageFrom property. * * @param messageFrom * The messageFrom value. * @return Instance of this to support fluent API. */ public CommandSender setMessageFrom(final MessageFrom messageFrom) { this.messageFrom = messageFrom; return this; } /** * Gets the value of commandPrefix property value. * * @return The commandPrefix value. */ protected String getCommandPrefix() { return commandPrefix; } /** * Sets the value of commandPrefix property value. * * @param commandPrefix * The commandPrefix value. * @return Instance of this to support fluent API. */ protected CommandSender setCommandPrefix(final String commandPrefix) { this.commandPrefix = commandPrefix; return this; } /** * Gets an array of environment variables. * * @return The environment variables array. */ public String[] getEnvironmentVariables() { return Arrays.copyOf(environmentVariables, environmentVariables.length); // do not allow external modifications } /** * Sets the environment variables from an array. * * @param environmentVariables * The environment variables array. * @return Instance of this to support fluent API. */ public CommandSender setEnvironmentVariables(final String[] environmentVariables) { this.environmentVariables = Arrays.copyOf(environmentVariables, environmentVariables.length); // ignore any later external modifications return this; } }