org.springframework.integration.aws.outbound.SnsMessageHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.aws.outbound.SnsMessageHandler.java

Source

/*
 * Copyright 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.springframework.integration.aws.outbound;

import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.support.StandardTypeLocator;
import org.springframework.integration.aws.support.AwsHeaders;
import org.springframework.integration.aws.support.SnsBodyBuilder;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;

import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.model.PublishRequest;
import com.amazonaws.services.sns.model.PublishResult;

/**
 * The {@link AbstractReplyProducingMessageHandler} implementation to send SNS Notifications
 * ({@link AmazonSNS#publish(PublishRequest)}) to the provided {@code topicArn}
 * (or evaluated at runtime against {@link Message}).
 * <p>
 * The SNS Message subject can be evaluated as a result of {@link #subjectExpression}.
 * <p>
 * The algorithm to populate SNS Message body is like:
 * <ul>
 * <li>
 * If the {@code payload instanceof PublishRequest} it is used as is for publishing.
 * </li>
 * <li>
 * If the {@link #bodyExpression} is specified, it is used to be evaluated
 * against {@code requestMessage}.
 * </li>
 * <li>
 * If the evaluation result (or {@code payload}) is instance of {@link SnsBodyBuilder},
 * the SNS Message is built from there and the {@code messageStructure}
 * of the {@link PublishRequest} is set to {@code json}.
 * For the convenience the package {@code org.springframework.integration.aws.support} is imported
 * to the {@link #evaluationContext} to allow bypass it for the {@link SnsBodyBuilder}
 * from the {@link #bodyExpression} definition. For example:
 * <pre class="code">
 * {@code
 * String bodyExpression =
 * "SnsBodyBuilder.withDefault(payload).forProtocols(payload.substring(0, 140), 'sms')";
 * snsMessageHandler.setBodyExpression(spelExpressionParser.parseExpression(bodyExpression));
 * }
 * </pre>
 * </li>
 * <li>
 * Otherwise the {@code payload} (or the {@link #bodyExpression} evaluation result) is converted
 * to the {@link String} using {@link #getConversionService()}.
 * </li>
 * </ul>
 * <p>
 * If this {@link AbstractReplyProducingMessageHandler} is configured with {@link #produceReply} as
 * {@code true}, the reply message is composed to be sent to the {@code outputChannel} or
 * {@code replyChannel}. The reply message's {@code payload} is exactly the {@link PublishRequest}
 * object, which has been just published to SNS. Also this message has {@link AwsHeaders#TOPIC}
 * and {@link AwsHeaders#SNS_PUBLISHED_MESSAGE_ID} headers to track published SNS message in the
 * downstream.
 *
 * @author Artem Bilan
 *
 * @see AmazonSNS
 * @see PublishRequest
 * @see SnsBodyBuilder
 */
public class SnsMessageHandler extends AbstractReplyProducingMessageHandler {

    private final AmazonSNS amazonSns;

    private final boolean produceReply;

    private EvaluationContext evaluationContext;

    private Expression topicArnExpression;

    private Expression subjectExpression;

    private Expression bodyExpression;

    private ResourceIdResolver resourceIdResolver;

    public SnsMessageHandler(AmazonSNS amazonSns) {
        this(amazonSns, false);
    }

    public SnsMessageHandler(AmazonSNS amazonSns, boolean produceReply) {
        Assert.notNull(amazonSns, "amazonSns must not be null.");
        this.amazonSns = amazonSns;
        this.produceReply = produceReply;
    }

    public void setTopicArn(String topicArn) {
        Assert.hasText(topicArn, "topicArn must not be empty.");
        this.topicArnExpression = new LiteralExpression(topicArn);
    }

    public void setTopicArnExpression(Expression topicArnExpression) {
        Assert.notNull(topicArnExpression, "topicArnExpression must not be null.");
        this.topicArnExpression = topicArnExpression;
    }

    public void setSubject(String subject) {
        Assert.hasText(subject, "subject must not be empty.");
        this.subjectExpression = new LiteralExpression(subject);
    }

    public void setSubjectExpression(Expression subjectExpression) {
        Assert.notNull(subjectExpression, "subjectExpression must not be null.");
        this.subjectExpression = subjectExpression;
    }

    /**
     * The {@link Expression} to produce the SNS notification message.
     * If it evaluates to the {@link SnsBodyBuilder} the {@code messageStructure}
     * of the {@link PublishRequest} is set to {@code json}.
     * Otherwise the {@link #getConversionService()} is used to convert the evaluation result
     * to the {@link String} without setting the {@code messageStructure}.
     * @param bodyExpression the {@link Expression} to produce the SNS notification message.
     */
    public void setBodyExpression(Expression bodyExpression) {
        Assert.notNull(bodyExpression, "bodyExpression must not be null.");
        this.bodyExpression = bodyExpression;
    }

    /**
     * Specify a {@link ResourceIdResolver} to resolve logical topic names to physical resource ids.
     * @param resourceIdResolver the {@link ResourceIdResolver} to use.
     */
    public void setResourceIdResolver(ResourceIdResolver resourceIdResolver) {
        this.resourceIdResolver = resourceIdResolver;
    }

    @Override
    protected void doInit() {
        super.doInit();
        this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
        TypeLocator typeLocator = this.evaluationContext.getTypeLocator();
        if (typeLocator instanceof StandardTypeLocator) {
            /*
             * Register the 'org.springframework.integration.aws.support' package
             * you don't need a FQCN for the 'SnsMessageBuilder'.
             */
            ((StandardTypeLocator) typeLocator).registerImport("org.springframework.integration.aws.support");
        }
    }

    @Override
    protected Object handleRequestMessage(Message<?> requestMessage) {
        Object payload = requestMessage.getPayload();

        PublishRequest publishRequest = null;

        if (payload instanceof PublishRequest) {
            publishRequest = (PublishRequest) payload;
        } else {
            Assert.state(this.topicArnExpression != null, "'topicArn' or 'topicArnExpression' must be specified.");
            publishRequest = new PublishRequest();
            String topicArn = this.topicArnExpression.getValue(this.evaluationContext, requestMessage,
                    String.class);
            if (this.resourceIdResolver != null) {
                topicArn = this.resourceIdResolver.resolveToPhysicalResourceId(topicArn);
            }
            publishRequest.setTopicArn(topicArn);

            if (this.subjectExpression != null) {
                String subject = this.subjectExpression.getValue(this.evaluationContext, requestMessage,
                        String.class);
                publishRequest.setSubject(subject);
            }

            Object snsMessage = requestMessage.getPayload();

            if (this.bodyExpression != null) {
                snsMessage = this.bodyExpression.getValue(this.evaluationContext, requestMessage);
            }

            if (snsMessage instanceof SnsBodyBuilder) {
                publishRequest.withMessageStructure("json").setMessage(((SnsBodyBuilder) snsMessage).build());
            } else {
                publishRequest.setMessage(getConversionService().convert(snsMessage, String.class));
            }
        }

        PublishResult publishResult = this.amazonSns.publish(publishRequest);

        if (this.produceReply) {
            return getMessageBuilderFactory().withPayload(publishRequest)
                    .setHeader(AwsHeaders.TOPIC, publishRequest.getTopicArn())
                    .setHeader(AwsHeaders.SNS_PUBLISHED_MESSAGE_ID, publishResult.getMessageId());
        } else {
            return null;
        }
    }

}