/* * 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.cloud.stream.binder; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.cloud.stream.provisioning.ConsumerDestination; import org.springframework.cloud.stream.provisioning.ProducerDestination; import org.springframework.cloud.stream.provisioning.ProvisioningException; import org.springframework.cloud.stream.provisioning.ProvisioningProvider; import org.springframework.context.Lifecycle; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.channel.FixedSubscriberChannel; import org.springframework.integration.core.MessageProducer; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.SubscribableChannel; import org.springframework.util.Assert; import org.springframework.util.MimeType; /** * {@link AbstractBinder} that serves as base class for {@link MessageChannel} * binders. Implementors must implement the following methods: * <ul> * <li>{@link #createProducerMessageHandler(ProducerDestination, ProducerProperties)} </li> * <li>{@link #createConsumerEndpoint(ConsumerDestination, String, ConsumerProperties)} </li> * </ul> * * @param <C> the consumer properties type * @param <P> the producer properties type * @author Marius Bogoevici * @author Ilayaperumal Gopinathan * @author Soby Chacko * @since 1.1 */ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties, P extends ProducerProperties, PP extends ProvisioningProvider<C, P>> extends AbstractBinder<MessageChannel, C, P> { protected static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); /** * Indicates whether the implementation and the message broker have * native support for message headers. If false, headers will be * embedded in the message payloads. */ private final boolean supportsHeadersNatively; /** * Indicates what headers are to be embedded in the payload if * {@link #supportsHeadersNatively} is true. */ private final String[] headersToEmbed; /** * {@link ProvisioningProvider} delegated by the downstream binder implementations. */ protected final PP provisioningProvider; public AbstractMessageChannelBinder(boolean supportsHeadersNatively, String[] headersToEmbed, PP provisioningProvider) { this.supportsHeadersNatively = supportsHeadersNatively; this.headersToEmbed = headersToEmbed == null ? new String[0] : headersToEmbed; this.provisioningProvider = provisioningProvider; } /** * Binds an outbound channel to a given destination. The implementation delegates to * {@link ProvisioningProvider#provisionProducerDestination(String, ProducerProperties)} * and {@link #createProducerMessageHandler(ProducerDestination, ProducerProperties)} for * handling the middleware specific logic. If the returned producer message handler is an * {@link InitializingBean} then {@link InitializingBean#afterPropertiesSet()} will be * called on it. Similarly, if the returned producer message handler e ndpoint is a * {@link Lifecycle}, then {@link Lifecycle#start()} will be called on it. * * @param destination the name of the destination * @param outputChannel the channel to be bound * @param producerProperties the {@link ProducerProperties} of the binding * @return the Binding for the channel * @throws BinderException on internal errors during binding */ @Override public final Binding<MessageChannel> doBindProducer(final String destination, MessageChannel outputChannel, final P producerProperties) throws BinderException { Assert.isInstanceOf(SubscribableChannel.class, outputChannel, "Binding is supported only for SubscribableChannel instances"); final MessageHandler producerMessageHandler; final ProducerDestination producerDestination; try { producerDestination = this.provisioningProvider.provisionProducerDestination(destination, producerProperties); producerMessageHandler = createProducerMessageHandler(producerDestination, producerProperties); if (producerMessageHandler instanceof InitializingBean) { ((InitializingBean) producerMessageHandler).afterPropertiesSet(); } } catch (Exception e) { if (e instanceof BinderException) { throw (BinderException) e; } else if (e instanceof ProvisioningException) { throw (ProvisioningException) e; } else { throw new BinderException("Exception thrown while building outbound endpoint", e); } } if (producerMessageHandler instanceof Lifecycle) { ((Lifecycle) producerMessageHandler).start(); } ((SubscribableChannel) outputChannel).subscribe( new SendingHandler(producerMessageHandler, !this.supportsHeadersNatively && HeaderMode.embeddedHeaders .equals(producerProperties.getHeaderMode()), this.headersToEmbed, producerProperties.isUseNativeEncoding())); return new DefaultBinding<MessageChannel>(destination, null, outputChannel, producerMessageHandler instanceof Lifecycle ? (Lifecycle) producerMessageHandler : null) { @Override public void afterUnbind() { try { if (producerMessageHandler instanceof DisposableBean) { ((DisposableBean) producerMessageHandler).destroy(); } } catch (Exception e) { AbstractMessageChannelBinder.this.logger.error("Exception thrown while unbinding " + this.toString(), e); } afterUnbindProducer(producerDestination, producerProperties); } }; } /** * Creates a {@link MessageHandler} with the ability to send data to the * target middleware. If the returned instance is also a {@link Lifecycle}, * it will be stopped automatically by the binder. * <p> * In order to be fully compliant, the {@link MessageHandler} of the binder * must observe the following headers: * <ul> * <li>{@link BinderHeaders#PARTITION_HEADER} - indicates the target * partition where the message must be sent</li> * </ul> * <p> * * @param destination the name of the target destination * @param producerProperties the producer properties * @return the message handler for sending data to the target middleware * @throws Exception */ protected abstract MessageHandler createProducerMessageHandler(ProducerDestination destination, P producerProperties) throws Exception; /** * Invoked after the unbinding of a producer. Subclasses may override this to provide * their own logic for dealing with unbinding. * * @param destination the bound destination * @param producerProperties the producer properties */ protected void afterUnbindProducer(ProducerDestination destination, P producerProperties) { } /** * Binds an inbound channel to a given destination. The implementation delegates to * {@link ProvisioningProvider#provisionConsumerDestination(String, String, ConsumerProperties)} * and {@link #createConsumerEndpoint(ConsumerDestination, String, ConsumerProperties)} * for handling middleware-specific logic. If the returned consumer endpoint is an * {@link InitializingBean} then {@link InitializingBean#afterPropertiesSet()} will be * called on it. Similarly, if the returned consumer endpoint is a {@link Lifecycle}, * then {@link Lifecycle#start()} will be called on it. * * @param name the name of the destination * @param group the consumer group * @param inputChannel the channel to be bound * @param properties the {@link ConsumerProperties} of the binding * @return the Binding for the channel * @throws BinderException on internal errors during binding */ @Override public final Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel, final C properties) throws BinderException { MessageProducer consumerEndpoint = null; try { final ConsumerDestination destination = this.provisioningProvider.provisionConsumerDestination(name, group, properties); final boolean extractEmbeddedHeaders = HeaderMode.embeddedHeaders.equals( properties.getHeaderMode()) && !this.supportsHeadersNatively; ReceivingHandler rh = new ReceivingHandler(extractEmbeddedHeaders); rh.setOutputChannel(inputChannel); final FixedSubscriberChannel bridge = new FixedSubscriberChannel(rh); bridge.setBeanName("bridge." + name); consumerEndpoint = createConsumerEndpoint(destination, group, properties); consumerEndpoint.setOutputChannel(bridge); if (consumerEndpoint instanceof InitializingBean) { ((InitializingBean) consumerEndpoint).afterPropertiesSet(); } if (consumerEndpoint instanceof Lifecycle) { ((Lifecycle) consumerEndpoint).start(); } final Object endpoint = consumerEndpoint; EventDrivenConsumer edc = new EventDrivenConsumer(bridge, rh); edc.setBeanName("inbound." + groupedName(name, group)); edc.start(); return new DefaultBinding<MessageChannel>(name, group, inputChannel, endpoint instanceof Lifecycle ? (Lifecycle) endpoint : null) { @Override protected void afterUnbind() { try { if (endpoint instanceof DisposableBean) { ((DisposableBean) endpoint).destroy(); } } catch (Exception e) { AbstractMessageChannelBinder.this.logger .error("Exception thrown while unbinding " + this.toString(), e); } AbstractMessageChannelBinder.this.afterUnbindConsumer(destination, this.group, properties); } }; } catch (Exception e) { if (consumerEndpoint instanceof Lifecycle) { ((Lifecycle) consumerEndpoint).stop(); } if (e instanceof BinderException) { throw (BinderException) e; } else if (e instanceof ProvisioningException) { throw (ProvisioningException) e; } else { throw new BinderException("Exception thrown while starting consumer: ", e); } } } /** * Creates {@link MessageProducer} that receives data from the consumer destination. * will be started and stopped by the binder. * * @param group the consumer group * @param destination reference to the consumer destination * @param properties the consumer properties * @return the consumer endpoint. */ protected abstract MessageProducer createConsumerEndpoint(ConsumerDestination destination, String group, C properties) throws Exception; /** * Invoked after the unbinding of a consumer. The binder implementation can override * this method to provide their own logic (e.g. for cleaning up destinations). * * @param destination the consumer destination * @param group the consumer group * @param consumerProperties the consumer properties */ protected void afterUnbindConsumer(ConsumerDestination destination, String group, C consumerProperties) { } private final class ReceivingHandler extends AbstractReplyProducingMessageHandler { private final boolean extractEmbeddedHeaders; private ReceivingHandler(boolean extractEmbeddedHeaders) { this.extractEmbeddedHeaders = extractEmbeddedHeaders; } @Override @SuppressWarnings("unchecked") protected Object handleRequestMessage(Message<?> requestMessage) { if (!(requestMessage.getPayload() instanceof byte[]) && !requestMessage.getHeaders().containsKey(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)) { return requestMessage; } MessageValues messageValues; if (this.extractEmbeddedHeaders) { try { messageValues = EmbeddedHeaderUtils.extractHeaders((Message<byte[]>) requestMessage, true); } catch (Exception e) { AbstractMessageChannelBinder.this.logger.error( EmbeddedHeaderUtils.decodeExceptionMessage( requestMessage), e); messageValues = new MessageValues(requestMessage); } messageValues = deserializePayloadIfNecessary(messageValues); } else { messageValues = deserializePayloadIfNecessary(requestMessage); } return messageValues.toMessage(); } @Override protected boolean shouldCopyRequestHeaders() { // prevent the message from being copied again in superclass return false; } } private final class SendingHandler extends AbstractMessageHandler implements Lifecycle { private final boolean embedHeaders; private final String[] embeddedHeaders; private final MessageHandler delegate; private final boolean useNativeEncoding; private SendingHandler(MessageHandler delegate, boolean embedHeaders, String[] headersToEmbed, boolean useNativeEncoding) { this.delegate = delegate; this.setBeanFactory(AbstractMessageChannelBinder.this.getBeanFactory()); this.embedHeaders = embedHeaders; this.embeddedHeaders = headersToEmbed; this.useNativeEncoding = useNativeEncoding; } @Override protected void handleMessageInternal(Message<?> message) throws Exception { Message<?> messageToSend = (this.useNativeEncoding) ? message : serializeAndEmbedHeadersIfApplicable(message); this.delegate.handleMessage(messageToSend); } private Message<?> serializeAndEmbedHeadersIfApplicable(Message<?> message) throws Exception { MessageValues transformed = serializePayloadIfNecessary(message); byte[] payload; if (this.embedHeaders) { Object contentType = transformed.get(MessageHeaders.CONTENT_TYPE); // transform content type headers to String, so that they can be properly embedded in JSON if (contentType instanceof MimeType) { transformed.put(MessageHeaders.CONTENT_TYPE, contentType.toString()); } Object originalContentType = transformed.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE); if (originalContentType instanceof MimeType) { transformed.put(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE, originalContentType.toString()); } payload = EmbeddedHeaderUtils.embedHeaders(transformed, this.embeddedHeaders); } else { payload = (byte[]) transformed.getPayload(); } return getMessageBuilderFactory().withPayload(payload).copyHeaders(transformed.getHeaders()).build(); } @Override public void start() { if (this.delegate instanceof Lifecycle) { ((Lifecycle) this.delegate).start(); } } @Override public void stop() { if (this.delegate instanceof Lifecycle) { ((Lifecycle) this.delegate).stop(); } } @Override public boolean isRunning() { return this.delegate instanceof Lifecycle && ((Lifecycle) this.delegate).isRunning(); } } }