All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.consol.citrus.actions.SendMessageAction Maven / Gradle / Ivy

/*
 * Copyright 2006-2010 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 com.consol.citrus.actions;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import com.consol.citrus.CitrusSettings;
import com.consol.citrus.Completable;
import com.consol.citrus.context.TestContext;
import com.consol.citrus.endpoint.Endpoint;
import com.consol.citrus.exceptions.CitrusRuntimeException;
import com.consol.citrus.message.Message;
import com.consol.citrus.message.MessageBuilder;
import com.consol.citrus.message.MessageDirection;
import com.consol.citrus.message.MessageProcessor;
import com.consol.citrus.message.MessageType;
import com.consol.citrus.message.builder.MessageBuilderSupport;
import com.consol.citrus.message.builder.SendMessageBuilderSupport;
import com.consol.citrus.util.IsJsonPredicate;
import com.consol.citrus.util.IsXmlPredicate;
import com.consol.citrus.validation.SchemaValidator;
import com.consol.citrus.validation.context.SchemaValidationContext;
import com.consol.citrus.validation.json.JsonMessageValidationContext;
import com.consol.citrus.validation.xml.XmlMessageValidationContext;
import com.consol.citrus.variable.VariableExtractor;
import com.consol.citrus.variable.dictionary.DataDictionary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.StringUtils;


/**
 * This action sends a messages to a specified message endpoint. The action holds a reference to
 * a {@link com.consol.citrus.endpoint.Endpoint}, which is capable of the message transport implementation. So action is
 * independent of the message transport configuration.
 *
 * @author Christoph Deppisch
 * @since 2008
 */
public class SendMessageAction extends AbstractTestAction implements Completable {

    private static final String REPORT_ENABLED_ENV = "CITRUS_SUMMARY_REPORT_ENABLED";
    /** Message endpoint instance */
    private final Endpoint endpoint;

    /** Message endpoint uri - either bean name or dynamic uri */
    private final String endpointUri;

    /**
     * Should message be validated with its schema definition
     */
    private final boolean schemaValidation;

    /** Explicit schema repository to use for this validation */
    private final String schemaRepository;

    /** Explicit schema instance to use for this validation */
    private final String schema;

    /** List of variable extractors responsible for creating variables from received message content */
    private final List variableExtractors;

    /** List of message processors responsible for manipulating message to be sent */
    private final List messageProcessors;

    /** Builder constructing a control message */
    private final MessageBuilder messageBuilder;

    /** Forks the message sending action so other actions can take place while this
     * message sender is waiting for the synchronous response */
    private final boolean forkMode;

    /** The message type to send in this action - this information is needed to find proper
     * message construction processors for this message */
    private final String messageType;

    /** Optional data dictionary that explicitly modifies message content before sending */
    private final DataDictionary dataDictionary;

    /** Finished indicator either called when forked send action is finished or immediately when this action has finished */
    private CompletableFuture finished;

    /** Logger */
    private static final Logger LOG = LoggerFactory.getLogger(SendMessageAction.class);

    /**
     * Default constructor.
     */
    public SendMessageAction(SendMessageActionBuilder builder) {
        super("send", builder);

        this.forkMode = builder.forkMode;
        this.endpoint = builder.getEndpoint();
        this.endpointUri = builder.getEndpointUri();
        this.schemaValidation = builder.getMessageBuilderSupport().isSchemaValidation();
        this.schema = builder.getMessageBuilderSupport().getSchema();
        this.schemaRepository = builder.getMessageBuilderSupport().getSchemaRepository();
        this.variableExtractors = builder.getVariableExtractors();
        this.messageProcessors = builder.getMessageProcessors();
        this.messageBuilder = builder.getMessageBuilderSupport().getMessageBuilder();
        this.messageType = builder.getMessageBuilderSupport().getMessageType();
        this.dataDictionary = builder.getMessageBuilderSupport().getDataDictionary();
    }

    /**
     * Message is constructed with payload and header entries and sent via
     * {@link com.consol.citrus.endpoint.Endpoint} instance.
     */
    @Override
    public void doExecute(final TestContext context) {
        final Message message = createMessage(context, messageType);
        finished = new CompletableFuture<>();

        // extract variables from before sending message so we can save dynamic message ids
        for (VariableExtractor variableExtractor : variableExtractors) {
            variableExtractor.extractVariables(message, context);
        }

        final Endpoint messageEndpoint = getOrCreateEndpoint(context);

        if (StringUtils.hasText(message.getName())) {
            context.getMessageStore().storeMessage(message.getName(), message);
        } else {
            context.getMessageStore().storeMessage(context.getMessageStore().constructMessageName(this, messageEndpoint), message);
        }

        if (forkMode) {
            LOG.debug("Forking message sending action ...");

            SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
            taskExecutor.execute(() -> {
                try {
                    validateMessage(message, context);
                    messageEndpoint.createProducer().send(message, context);
                } catch (Exception e) {
                    if (e instanceof CitrusRuntimeException) {
                        context.addException((CitrusRuntimeException) e);
                    } else {
                        context.addException(new CitrusRuntimeException(e));
                    }
                } finally {
                    finished.complete(null);
                }
            });
        } else {
            try {
                validateMessage(message, context);
                messageEndpoint.createProducer().send(message, context);
            } finally {
                finished.complete(null);
            }
        }
    }

    /**
     * Validate the message against registered schemas.
     * @param message
     */
    protected void validateMessage(Message message, TestContext context) {

        List> schemaValidators = null;
        SchemaValidationContext validationContext = null;
        String  payload = message.getPayload(String.class);

        if ((isSchemaValidation() || isJsonSchemaValidationEnabled()) && IsJsonPredicate.getInstance().test(payload)) {
            schemaValidators = context.getMessageValidatorRegistry()
                    .findSchemaValidators(MessageType.JSON.name(), message);
            validationContext = JsonMessageValidationContext.Builder.json()
                    .schemaValidation(this.schemaValidation)
                    .schema(this.schema)
                    .schemaRepository(this.schemaRepository).build();
        } else if ((isSchemaValidation() || isXmlSchemaValidationEnabled()) && IsXmlPredicate.getInstance().test(payload)) {
            schemaValidators = context.getMessageValidatorRegistry()
                    .findSchemaValidators(MessageType.XML.name(), message);
            validationContext = XmlMessageValidationContext.Builder.xml()
                    .schemaValidation(this.schemaValidation)
                    .schema(this.schema)
                    .schemaRepository(this.schemaRepository).build();
        }

        if (schemaValidators != null) {
            for (SchemaValidator validator : schemaValidators) {
                validator.validate(message, context, validationContext);
            }
        }

    }

    /**
     * Get setting to determine if json schema validation is enabled by default.
     * @return
     */
    private static boolean isJsonSchemaValidationEnabled() {
        return Boolean.getBoolean(CitrusSettings.OUTBOUND_SCHEMA_VALIDATION_ENABLED_PROPERTY)
                || Boolean.getBoolean(CitrusSettings.OUTBOUND_JSON_SCHEMA_VALIDATION_ENABLED_PROPERTY)
                || Boolean.parseBoolean(System.getenv(CitrusSettings.OUTBOUND_SCHEMA_VALIDATION_ENABLED_ENV))
                || Boolean.parseBoolean(System.getenv(CitrusSettings.OUTBOUND_JSON_SCHEMA_VALIDATION_ENABLED_ENV));
    }

    /**
     * Get setting to determine if xml schema validation is enabled by default.
     * @return
     */
    private static boolean isXmlSchemaValidationEnabled() {
        return Boolean.getBoolean(CitrusSettings.OUTBOUND_SCHEMA_VALIDATION_ENABLED_PROPERTY)
                || Boolean.getBoolean(CitrusSettings.OUTBOUND_XML_SCHEMA_VALIDATION_ENABLED_PROPERTY)
                || Boolean.parseBoolean(System.getenv(CitrusSettings.OUTBOUND_SCHEMA_VALIDATION_ENABLED_ENV))
                || Boolean.parseBoolean(System.getenv(CitrusSettings.OUTBOUND_XML_SCHEMA_VALIDATION_ENABLED_ENV));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDisabled(TestContext context) {
        Endpoint messageEndpoint = getOrCreateEndpoint(context);
        if (getActor() == null && messageEndpoint.getActor() != null) {
            return messageEndpoint.getActor().isDisabled();
        }

        return super.isDisabled(context);
    }

    @Override
    public boolean isDone(TestContext context) {
        return Optional.ofNullable(finished)
                .map(future -> future.isDone() || isDisabled(context))
                .orElse(isDisabled(context));
    }

    /**
     * Create message to be sent.
     * @param context
     * @param messageType
     * @return
     */
    protected Message createMessage(TestContext context, String messageType) {
        Message message = messageBuilder.build(context, messageType);

        if (message.getPayload() != null) {
            context.getMessageProcessors(MessageDirection.OUTBOUND)
                    .forEach(processor -> processor.process(message, context));

            if (dataDictionary != null) {
                dataDictionary.process(message, context);
            }

            messageProcessors.forEach(processor -> processor.process(message, context));
        }

        return message;
    }

    /**
     * Creates or gets the message endpoint instance.
     * @return the message endpoint
     */
    public Endpoint getOrCreateEndpoint(TestContext context) {
        if (endpoint != null) {
            return endpoint;
        } else if (StringUtils.hasText(endpointUri)) {
            return context.getEndpointFactory().create(endpointUri, context);
        } else {
            throw new CitrusRuntimeException("Neither endpoint nor endpoint uri is set properly!");
        }
    }

    /**
     * Gets the message endpoint.
     * @return
     */
    public Endpoint getEndpoint() {
        return endpoint;
    }

    /**
     * Get
     * @return true if schema validation is active for this message
     */
    public boolean isSchemaValidation() {
        return schemaValidation;
    }

    /**
     * Get the name of the schema repository used for validation
     * @return the schema repository name
     */
    public String getSchemaRepository() {
        return schemaRepository;
    }

    /**
     * Get the name of the schema used for validation
     * @return the schema
     */
    public String getSchema() {
        return schema;
    }

    /**
     * Get the variable extractors.
     * @return the variableExtractors
     */
    public List getVariableExtractors() {
        return variableExtractors;
    }

    /**
     * Obtains the message processors.
     * @return
     */
    public List getMessageProcessors() {
        return messageProcessors;
    }

    /**
     * Gets the messageBuilder.
     * @return the messageBuilder
     */
    public MessageBuilder getMessageBuilder() {
        return messageBuilder;
    }

    /**
     * Gets the forkMode.
     * @return the forkMode the forkMode to get.
     */
    public boolean isForkMode() {
        return forkMode;
    }

    /**
     * Gets the message type for this receive action.
     * @return the messageType
     */
    public String getMessageType() {
        return messageType;
    }

    /**
     * Gets the data dictionary.
     * @return
     */
    public DataDictionary getDataDictionary() {
        return dataDictionary;
    }

    /**
     * Gets the endpoint uri.
     * @return
     */
    public String getEndpointUri() {
        return endpointUri;
    }

    /**
     * Action builder.
     */
    public static final class Builder extends SendMessageActionBuilder {

        /**
         * Fluent API action building entry method used in Java DSL.
         * @return
         */
        public static Builder send() {
            return new Builder();
        }

        /**
         * Fluent API action building entry method used in Java DSL.
         * @param messageEndpoint
         * @return
         */
        public static Builder send(Endpoint messageEndpoint) {
            Builder builder = new Builder();
            builder.endpoint(messageEndpoint);
            return builder;
        }

        /**
         * Fluent API action building entry method used in Java DSL.
         * @param messageEndpointUri
         * @return
         */
        public static Builder send(String messageEndpointUri) {
            Builder builder = new Builder();
            builder.endpoint(messageEndpointUri);
            return builder;
        }

        @Override
        public SendMessageActionBuilderSupport getMessageBuilderSupport() {
            if (messageBuilderSupport == null) {
                messageBuilderSupport = new SendMessageActionBuilderSupport(self);
            }
            return super.getMessageBuilderSupport();
        }

        @Override
        public SendMessageAction doBuild() {
            return new SendMessageAction(this);
        }

    }

    public static class SendMessageActionBuilderSupport extends SendMessageBuilderSupport {

        public SendMessageActionBuilderSupport(SendMessageAction.Builder delegate) {
            super(delegate);
        }
    }

    /**
     * Base send message action builder also used by subclasses of base send message action.
     */
    public static abstract class SendMessageActionBuilder, B extends SendMessageActionBuilder>
            extends MessageBuilderSupport.MessageActionBuilder {

        protected boolean forkMode = false;
        protected CompletableFuture finished;

        /**
         * Sets the fork mode for this send action builder.
         * @param forkMode
         * @return
         */
        public B fork(boolean forkMode) {
            this.forkMode = forkMode;
            return self;
        }

        @Override
        public final T build() {
            if (messageBuilderSupport == null) {
                messageBuilderSupport = getMessageBuilderSupport();
            }

            if (referenceResolver != null) {
                if (messageBuilderSupport.getDataDictionaryName() != null) {
                    this.messageBuilderSupport.dictionary(
                            referenceResolver.resolve(messageBuilderSupport.getDataDictionaryName(), DataDictionary.class));
                }
            }

            return doBuild();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy