package br.com.microsoft.ocp.bot.service.jmeter.sampler; /* * 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. * */ import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.samplers.AbstractSampler; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterVariables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; import com.mashape.unirest.http.exceptions.UnirestException; import com.mashape.unirest.request.body.RequestBodyEntity; import br.com.microsoft.ocp.bot.service.jmeter.auth.AuthHelper; import br.com.microsoft.ocp.bot.service.jmeter.auth.AuthenticationException; import br.com.microsoft.ocp.bot.service.jmeter.auth.TokenResponse; import br.com.microsoft.ocp.bot.service.jmeter.callback.server.HttpResponseException; import br.com.microsoft.ocp.bot.service.jmeter.config.BotServiceConfig; import br.com.microsoft.ocp.bot.service.jmeter.config.BotServiceSecurityConfig; import br.com.microsoft.ocp.bot.service.jmeter.plugin.schemas.Activity; import br.com.microsoft.ocp.bot.service.jmeter.plugin.schemas.Attachment; import br.com.microsoft.ocp.bot.service.jmeter.plugin.schemas.CardAction; import br.com.microsoft.ocp.bot.service.jmeter.plugin.schemas.Message;; public abstract class BaseBotSampler extends AbstractSampler { private static final String CONTENT_TYPE_APPLICATION_JSON = "application/json"; private static final String CONTENT_TYPE_HEADER_KEY = "Content-Type"; private static final Logger log = LoggerFactory.getLogger(BaseBotSampler.class); private static final long serialVersionUID = 240L; public static final String NEW_LINE = "\r\n"; public static final String RESPONSE_NUMBER = "RESPONSE %d: "; public static final String ATTACHMENT_NUMBER = "ATTACHMENT %d: "; public static final String SUGGESTED_ACTION_NUMBER = "SUGGESTED ACTION %d: "; private static final String TOKEN = "TOKEN"; private static final String AUTHORIZATION_HEADER = "Authorization"; protected void ensureResponseSuccess(@SuppressWarnings("rawtypes") HttpResponse response) throws HttpResponseException { if (response.getStatus() > 400) { throw new HttpResponseException(response.getStatus(), response.getStatusText()); } } protected String getFromUser() { JMeterContext context = getThreadContext(); JMeterVariables vars = context.getVariables(); if (getGenRandomUserIdPerThread()) { return vars.get(Constants.USER); } else { return getFromMemberId(); } } protected SampleResult setErrorResponse(String message, Exception e, SampleResult res) { log.error(message, e); res.setResponseData(e.getMessage(), "UTF-8"); res.sampleEnd(); res.setSuccessful(false); return res; } @SuppressWarnings("unchecked") protected <T> List<T> getResponses(Activity requestActivity, BlockingQueue<Activity> queue, int numberOfResponseMessagesExpected, Class<T> clazz) throws InterruptedException, TimeoutException { log.info(String.format("Waiting for %d response(s) of activity %s at url %s", numberOfResponseMessagesExpected, requestActivity.getId(), requestActivity.getServiceUrl())); List<T> responses = new ArrayList<>(); while (true) { Activity activity; try { activity = queue.poll(getResponseTimeout(), TimeUnit.SECONDS); if (activity == null) { throw new TimeoutException( String.format("Timeout waiting for response. Activity: %s. Conversation: %s", requestActivity.getId(), requestActivity.getConversation().getId())); } if (clazz.isInstance(activity)) { responses.add((T) activity); if (responses.size() >= numberOfResponseMessagesExpected) { log.info(String.format("Got all (%d) responses for activity %s", responses.size(), requestActivity.getId())); break; } else { log.info(String.format("Got %d responses for activity %s", responses.size(), requestActivity.getId())); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw e; } } return responses; } protected Activity send(Activity activity) throws UnirestException, HttpResponseException, AuthenticationException { log.info(String.format( "Sending activity of type %s with activityId=%s, conversationId=%s with callbackUrl as %s", activity.getType(), activity.getId(), activity.getConversation().getId(), activity.getServiceUrl())); Jsonb jsonb = JsonbBuilder.create(); String jsonPayload = jsonb.toJson(activity); log.debug("Sending payload: " + jsonPayload); RequestBodyEntity request = Unirest.post(getBotUrl()).body(jsonPayload); Map<String, List<String>> headers = request.getHttpRequest().getHeaders(); headers.put(CONTENT_TYPE_HEADER_KEY, Collections.singletonList(CONTENT_TYPE_APPLICATION_JSON)); if (hasAuthProperties()) { JMeterVariables vars = getThreadContext().getVariables(); log.debug("Has security data"); if (vars.get(TOKEN) == null) { log.debug("Token is null, authenticating"); TokenResponse tokenResponse = AuthHelper.getToken(getPropertyAsString(BotServiceSecurityConfig.APP_ID), getPropertyAsString(BotServiceSecurityConfig.CLIENT_SECRET)); vars.put(TOKEN, tokenResponse.getAccessToken()); } else { log.debug("Token is not null, will reuse it"); } String token = vars.get(TOKEN); headers.put(AUTHORIZATION_HEADER, Collections.singletonList("Bearer " + token)); } else { log.debug("No security properties available. Will proceed without authentication."); } HttpResponse<InputStream> postResponse = request.asBinary(); ensureResponseSuccess(postResponse); return activity; } private boolean hasAuthProperties() { return StringUtils.isNotEmpty(getPropertyAsString(BotServiceSecurityConfig.APP_ID)); } protected String getFullResponseText(List<Message> responses) { String respText = getTextResponse(responses); String attachmentsText = getAttachmentsResponseAsJsonString(responses); if (attachmentsText != null) { respText += attachmentsText; } String suggestedActionsText = getSuggestedActionsResponseAsJsonString(responses); if (suggestedActionsText != null) { respText += suggestedActionsText; } return respText; } protected String getTextResponse(List<Message> responses) { String respText = ""; int i = 1; for (Message message : responses) { respText += String.format(RESPONSE_NUMBER, i++) + StringUtils.trimToEmpty(message.getText()) + NEW_LINE; } return respText + NEW_LINE; } protected String getAttachmentsResponseAsJsonString(List<Message> responses) { String attachmentsJsonAsString = ""; Jsonb jsonb = JsonbBuilder.create(); int i = 1; for (Message message : responses) { if (message.getAttachments() != null && message.getAttachments().size() > 0) { for (Attachment attachment : message.getAttachments()) { String attachmentPayload = jsonb.toJson(attachment); attachmentsJsonAsString += String.format(ATTACHMENT_NUMBER, i++) + attachmentPayload + NEW_LINE; } } } if (attachmentsJsonAsString.length() == 0) { attachmentsJsonAsString = null; return null; } return StringUtils.trimToEmpty(attachmentsJsonAsString + NEW_LINE); } protected String getSuggestedActionsResponseAsJsonString(List<Message> responses) { String suggestedActionsJsonAsString = ""; Jsonb jsonb = JsonbBuilder.create(); int i = 1; for (Message message : responses) { if (message.getSuggestedActions() != null && message.getSuggestedActions().getActions() != null && message.getSuggestedActions().getActions().size() > 0) { for (CardAction action : message.getSuggestedActions().getActions()) { String payload = jsonb.toJson(action); suggestedActionsJsonAsString += String.format(SUGGESTED_ACTION_NUMBER, i++) + payload + NEW_LINE; } } } if (suggestedActionsJsonAsString.length() == 0) { suggestedActionsJsonAsString = null; return null; } return StringUtils.trimToEmpty(suggestedActionsJsonAsString + NEW_LINE); } public boolean getGenRandomUserIdPerThread() { return getPropertyAsBoolean(BotServiceConfig.GEN_RANDOM_ID_PER_THREAD); } public String getFromMemberId() { return getPropertyAsString(BotServiceConfig.FROM_MEMBER_ID); } public String getRecipientMemberId() { return getPropertyAsString(BotServiceConfig.RECIPIENT_MEMBER_ID); } public String getChannelId() { return getPropertyAsString(BotServiceConfig.CHANNEL_ID); } public String getCallbackUrl() { return getPropertyAsString(BotServiceConfig.CALLBACK_URL); } private String getBotUrl() { return getPropertyAsString(BotServiceConfig.BOT_URL); } private int getResponseTimeout() { return getPropertyAsInt(BotServiceConfig.RESPONSE_TIMEOUT); } }