Java tutorial
/* * Copyright 2002-2019 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 * * https://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.channel; import java.util.ArrayDeque; import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.commons.logging.Log; import org.springframework.beans.factory.BeanFactory; import org.springframework.core.OrderComparator; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.history.MessageHistory; import org.springframework.integration.support.management.AbstractMessageChannelMetrics; import org.springframework.integration.support.management.ConfigurableMetricsAware; import org.springframework.integration.support.management.DefaultMessageChannelMetrics; import org.springframework.integration.support.management.IntegrationManagedResource; import org.springframework.integration.support.management.TrackableComponent; import org.springframework.integration.support.management.metrics.MeterFacade; import org.springframework.integration.support.management.metrics.MetricsCaptor; import org.springframework.integration.support.management.metrics.SampleFacade; import org.springframework.integration.support.management.metrics.TimerFacade; import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageDeliveryException; import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Base class for {@link MessageChannel} implementations providing common * properties such as the channel name. Also provides the common functionality * for sending and receiving {@link Message Messages} including the invocation * of any {@link org.springframework.messaging.support.ChannelInterceptor ChannelInterceptors}. * * @author Mark Fisher * @author Oleg Zhurakousky * @author Gary Russell * @author Artem Bilan */ @IntegrationManagedResource @SuppressWarnings("deprecation") public abstract class AbstractMessageChannel extends IntegrationObjectSupport implements MessageChannel, TrackableComponent, ChannelInterceptorAware, org.springframework.integration.support.management.MessageChannelMetrics, ConfigurableMetricsAware<AbstractMessageChannelMetrics> { protected final ChannelInterceptorList interceptors; // NOSONAR private final Comparator<Object> orderComparator = new OrderComparator(); private final ManagementOverrides managementOverrides = new ManagementOverrides(); protected final Set<MeterFacade> meters = ConcurrentHashMap.newKeySet(); // NOSONAR private volatile boolean shouldTrack = false; private volatile Class<?>[] datatypes = new Class<?>[0]; private volatile String fullChannelName; private volatile MessageConverter messageConverter; private volatile boolean countsEnabled; private volatile boolean statsEnabled; private volatile boolean loggingEnabled = true; private volatile AbstractMessageChannelMetrics channelMetrics = new DefaultMessageChannelMetrics(); private MetricsCaptor metricsCaptor; private TimerFacade successTimer; private TimerFacade failureTimer; public AbstractMessageChannel() { this.interceptors = new ChannelInterceptorList(logger); } @Override public String getComponentType() { return "channel"; } @Override public void setShouldTrack(boolean shouldTrack) { this.shouldTrack = shouldTrack; } @Override public void registerMetricsCaptor(MetricsCaptor metricsCaptorToRegister) { this.metricsCaptor = metricsCaptorToRegister; } @Nullable protected MetricsCaptor getMetricsCaptor() { return this.metricsCaptor; } @Override public void setCountsEnabled(boolean countsEnabled) { this.countsEnabled = countsEnabled; this.managementOverrides.countsConfigured = true; if (!countsEnabled) { this.statsEnabled = false; this.managementOverrides.statsConfigured = true; } } @Override public boolean isCountsEnabled() { return this.countsEnabled; } @Override public void setStatsEnabled(boolean statsEnabled) { if (statsEnabled) { this.countsEnabled = true; this.managementOverrides.countsConfigured = true; } this.statsEnabled = statsEnabled; this.channelMetrics.setFullStatsEnabled(statsEnabled); this.managementOverrides.statsConfigured = true; } @Override public boolean isStatsEnabled() { return this.statsEnabled; } @Override public boolean isLoggingEnabled() { return this.loggingEnabled; } @Override public void setLoggingEnabled(boolean loggingEnabled) { this.loggingEnabled = loggingEnabled; this.managementOverrides.loggingConfigured = true; } protected AbstractMessageChannelMetrics getMetrics() { return this.channelMetrics; } @Override public void configureMetrics(AbstractMessageChannelMetrics metrics) { Assert.notNull(metrics, "'metrics' must not be null"); this.channelMetrics = metrics; this.managementOverrides.metricsConfigured = true; } /** * Specify the Message payload datatype(s) supported by this channel. If a * payload type does not match directly, but the 'conversionService' is * available, then type conversion will be attempted in the order of the * elements provided in this array. * <p> If this property is not set explicitly, any Message payload type will be * accepted. * @param datatypes The supported data types. * @see #setMessageConverter(MessageConverter) */ public void setDatatypes(Class<?>... datatypes) { this.datatypes = (datatypes != null && datatypes.length > 0) ? datatypes : new Class<?>[0]; } /** * Set the list of channel interceptors. This will clear any existing * interceptors. * @param interceptors The list of interceptors. */ @Override public void setInterceptors(List<ChannelInterceptor> interceptors) { Collections.sort(interceptors, this.orderComparator); this.interceptors.set(interceptors); } /** * Add a channel interceptor to the end of the list. * @param interceptor The interceptor. */ @Override public void addInterceptor(ChannelInterceptor interceptor) { this.interceptors.add(interceptor); } /** * Add a channel interceptor to the specified index of the list. * @param index The index to add interceptor. * @param interceptor The interceptor. */ @Override public void addInterceptor(int index, ChannelInterceptor interceptor) { this.interceptors.add(index, interceptor); } /** * Specify the {@link MessageConverter} to use when trying to convert to * one of this channel's supported datatypes (in order) for a Message whose payload * does not already match. * <p> <b>Note:</b> only the {@link MessageConverter#fromMessage(Message, Class)} * method is used. If the returned object is not a {@link Message}, the inbound * headers will be copied; if the returned object is a {@code Message}, it is * expected that the converter will have fully populated the headers; no * further action is performed by the channel. If {@code null} is returned, * conversion to the next datatype (if any) will be attempted. * Defaults to a * {@link org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter}. * @param messageConverter The message converter. */ public void setMessageConverter(MessageConverter messageConverter) { this.messageConverter = messageConverter; } /** * Return a read-only list of the configured interceptors. */ @Override public List<ChannelInterceptor> getInterceptors() { return this.interceptors.getInterceptors(); } @Override public boolean removeInterceptor(ChannelInterceptor interceptor) { return this.interceptors.remove(interceptor); } @Override @Nullable public ChannelInterceptor removeInterceptor(int index) { return this.interceptors.remove(index); } /** * Exposes the interceptor list instance for subclasses. * @return The channel interceptor list. */ protected ChannelInterceptorList getIChannelInterceptorList() { return this.interceptors; } @Override public void reset() { this.channelMetrics.reset(); } @Override public int getSendCount() { return this.channelMetrics.getSendCount(); } @Override public long getSendCountLong() { return this.channelMetrics.getSendCountLong(); } @Override public int getSendErrorCount() { return this.channelMetrics.getSendErrorCount(); } @Override public long getSendErrorCountLong() { return this.channelMetrics.getSendErrorCountLong(); } @Override public double getTimeSinceLastSend() { return this.channelMetrics.getTimeSinceLastSend(); } @Override public double getMeanSendRate() { return this.channelMetrics.getMeanSendRate(); } @Override public double getMeanErrorRate() { return this.channelMetrics.getMeanErrorRate(); } @Override public double getMeanErrorRatio() { return this.channelMetrics.getMeanErrorRatio(); } @Override public double getMeanSendDuration() { return this.channelMetrics.getMeanSendDuration(); } @Override public double getMinSendDuration() { return this.channelMetrics.getMinSendDuration(); } @Override public double getMaxSendDuration() { return this.channelMetrics.getMaxSendDuration(); } @Override public double getStandardDeviationSendDuration() { return this.channelMetrics.getStandardDeviationSendDuration(); } @Override public org.springframework.integration.support.management.Statistics getSendDuration() { return this.channelMetrics.getSendDuration(); } @Override public org.springframework.integration.support.management.Statistics getSendRate() { return this.channelMetrics.getSendRate(); } @Override public org.springframework.integration.support.management.Statistics getErrorRate() { return this.channelMetrics.getErrorRate(); } @Override public ManagementOverrides getOverrides() { return this.managementOverrides; } @Override protected void onInit() { super.onInit(); if (this.messageConverter == null) { BeanFactory beanFactory = getBeanFactory(); if (beanFactory != null && beanFactory.containsBean( IntegrationContextUtils.INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME)) { this.messageConverter = beanFactory.getBean( IntegrationContextUtils.INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME, MessageConverter.class); } } if (this.statsEnabled) { this.channelMetrics.setFullStatsEnabled(true); } this.fullChannelName = null; } /** * Returns the fully qualified channel name including the application context * id, if available. * * @return The name. */ public String getFullChannelName() { if (this.fullChannelName == null) { String contextId = getApplicationContextId(); String componentName = getComponentName(); componentName = (StringUtils.hasText(contextId) ? contextId + "." : "") + (StringUtils.hasText(componentName) ? componentName : "unknown.channel.name"); this.fullChannelName = componentName; } return this.fullChannelName; } /** * Send a message on this channel. If the channel is at capacity, this * method will block until either space becomes available or the sending * thread is interrupted. * @param message the Message to send * @return <code>true</code> if the message is sent successfully or * <code>false</code> if the sending thread is interrupted. */ @Override public boolean send(Message<?> message) { return this.send(message, -1); } /** * Send a message on this channel. If the channel is at capacity, this * method will block until either the timeout occurs or the sending thread * is interrupted. If the specified timeout is 0, the method will return * immediately. If less than zero, it will block indefinitely (see * {@link #send(Message)}). * @param messageArg the Message to send * @param timeout the timeout in milliseconds * @return <code>true</code> if the message is sent successfully, * <code>false</code> if the message cannot be sent within the allotted * time or the sending thread is interrupted. */ @Override // NOSONAR complexity public boolean send(Message<?> messageArg, long timeout) { Assert.notNull(messageArg, "message must not be null"); Assert.notNull(messageArg.getPayload(), "message payload must not be null"); Message<?> message = messageArg; if (this.shouldTrack) { message = MessageHistory.write(message, this, getMessageBuilderFactory()); } Deque<ChannelInterceptor> interceptorStack = null; boolean sent = false; boolean metricsProcessed = false; org.springframework.integration.support.management.MetricsContext metricsContext = null; boolean countsAreEnabled = this.countsEnabled; ChannelInterceptorList interceptorList = this.interceptors; AbstractMessageChannelMetrics metrics = this.channelMetrics; SampleFacade sample = null; try { message = convertPayloadIfNecessary(message); boolean debugEnabled = this.loggingEnabled && logger.isDebugEnabled(); if (debugEnabled) { logger.debug("preSend on channel '" + this + "', message: " + message); } if (interceptorList.getSize() > 0) { interceptorStack = new ArrayDeque<>(); message = interceptorList.preSend(message, this, interceptorStack); if (message == null) { return false; } } if (countsAreEnabled) { metricsContext = metrics.beforeSend(); if (this.metricsCaptor != null) { sample = this.metricsCaptor.start(); } sent = doSend(message, timeout); if (sample != null) { sample.stop(sendTimer(sent)); } metrics.afterSend(metricsContext, sent); metricsProcessed = true; } else { sent = doSend(message, timeout); } if (debugEnabled) { logger.debug("postSend (sent=" + sent + ") on channel '" + this + "', message: " + message); } if (interceptorStack != null) { interceptorList.postSend(message, this, sent); interceptorList.afterSendCompletion(message, this, sent, null, interceptorStack); } return sent; } catch (Exception ex) { if (countsAreEnabled && !metricsProcessed) { if (sample != null) { sample.stop(buildSendTimer(false, ex.getClass().getSimpleName())); } metrics.afterSend(metricsContext, false); } if (interceptorStack != null) { interceptorList.afterSendCompletion(message, this, sent, ex, interceptorStack); } throw IntegrationUtils.wrapInDeliveryExceptionIfNecessary(message, () -> "failed to send Message to channel '" + this.getComponentName() + "'", ex); } } private TimerFacade sendTimer(boolean sent) { if (sent) { if (this.successTimer == null) { this.successTimer = buildSendTimer(true, "none"); } return this.successTimer; } else { if (this.failureTimer == null) { this.failureTimer = buildSendTimer(false, "none"); } return this.failureTimer; } } private TimerFacade buildSendTimer(boolean success, String exception) { TimerFacade timer = this.metricsCaptor.timerBuilder(SEND_TIMER_NAME).tag("type", "channel") .tag("name", getComponentName() == null ? "unknown" : getComponentName()) .tag("result", success ? "success" : "failure").tag("exception", exception) .description("Send processing time").build(); this.meters.add(timer); return timer; } private Message<?> convertPayloadIfNecessary(Message<?> message) { if (this.datatypes.length > 0) { // first pass checks if the payload type already matches any of the datatypes for (Class<?> datatype : this.datatypes) { if (datatype.isAssignableFrom(message.getPayload().getClass())) { return message; } } if (this.messageConverter != null) { // second pass applies conversion if possible, attempting datatypes in order for (Class<?> datatype : this.datatypes) { Object converted = this.messageConverter.fromMessage(message, datatype); if (converted != null) { if (converted instanceof Message) { return (Message<?>) converted; } else { return getMessageBuilderFactory().withPayload(converted) .copyHeaders(message.getHeaders()).build(); } } } } throw new MessageDeliveryException(message, "Channel '" + this.getComponentName() + "' expected one of the following data types [" + StringUtils.arrayToCommaDelimitedString(this.datatypes) + "], but received [" + message.getPayload().getClass() + "]"); } else { return message; } } /** * Subclasses must implement this method. A non-negative timeout indicates * how long to wait if the channel is at capacity (if the value is 0, it * must return immediately with or without success). A negative timeout * value indicates that the method should block until either the message is * accepted or the blocking thread is interrupted. * @param message The message. * @param timeout The timeout. * @return true if the send was successful. */ protected abstract boolean doSend(Message<?> message, long timeout); @Override public void destroy() { this.meters.forEach(MeterFacade::remove); this.meters.clear(); } /** * A convenience wrapper class for the list of ChannelInterceptors. */ protected static class ChannelInterceptorList { protected final List<ChannelInterceptor> interceptors = new CopyOnWriteArrayList<>(); // NOSONAR private final Log logger; private int size; public ChannelInterceptorList(Log logger) { this.logger = logger; } public boolean set(List<ChannelInterceptor> interceptors) { synchronized (this.interceptors) { this.interceptors.clear(); this.size = interceptors.size(); return this.interceptors.addAll(interceptors); } } public int getSize() { return this.size; } public boolean add(ChannelInterceptor interceptor) { this.size++; return this.interceptors.add(interceptor); } public void add(int index, ChannelInterceptor interceptor) { this.size++; this.interceptors.add(index, interceptor); } @Nullable public Message<?> preSend(Message<?> messageArg, MessageChannel channel, Deque<ChannelInterceptor> interceptorStack) { Message<?> message = messageArg; if (this.size > 0) { for (ChannelInterceptor interceptor : this.interceptors) { Message<?> previous = message; message = interceptor.preSend(message, channel); if (message == null) { if (this.logger.isDebugEnabled()) { this.logger.debug(interceptor.getClass().getSimpleName() + " returned null from preSend, i.e. precluding the send."); } afterSendCompletion(previous, channel, false, null, interceptorStack); return null; } interceptorStack.add(interceptor); } } return message; } public void postSend(Message<?> message, MessageChannel channel, boolean sent) { if (this.size > 0) { for (ChannelInterceptor interceptor : this.interceptors) { interceptor.postSend(message, channel, sent); } } } public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, @Nullable Exception ex, Deque<ChannelInterceptor> interceptorStack) { for (Iterator<ChannelInterceptor> iterator = interceptorStack.descendingIterator(); iterator .hasNext();) { ChannelInterceptor interceptor = iterator.next(); try { interceptor.afterSendCompletion(message, channel, sent, ex); } catch (Exception ex2) { this.logger.error("Exception from afterSendCompletion in " + interceptor, ex2); } } } public boolean preReceive(MessageChannel channel, Deque<ChannelInterceptor> interceptorStack) { if (this.size > 0) { for (ChannelInterceptor interceptor : this.interceptors) { if (!interceptor.preReceive(channel)) { afterReceiveCompletion(null, channel, null, interceptorStack); return false; } interceptorStack.add(interceptor); } } return true; } @Nullable public Message<?> postReceive(Message<?> messageArg, MessageChannel channel) { Message<?> message = messageArg; if (this.size > 0) { for (ChannelInterceptor interceptor : this.interceptors) { message = interceptor.postReceive(message, channel); if (message == null) { return null; } } } return message; } public void afterReceiveCompletion(@Nullable Message<?> message, MessageChannel channel, @Nullable Exception ex, @Nullable Deque<ChannelInterceptor> interceptorStack) { if (interceptorStack != null) { for (Iterator<ChannelInterceptor> iterator = interceptorStack.descendingIterator(); iterator .hasNext();) { ChannelInterceptor interceptor = iterator.next(); try { interceptor.afterReceiveCompletion(message, channel, ex); } catch (Exception ex2) { this.logger.error("Exception from afterReceiveCompletion in " + interceptor, ex2); } } } } public List<ChannelInterceptor> getInterceptors() { return Collections.unmodifiableList(this.interceptors); } public boolean remove(ChannelInterceptor interceptor) { if (this.interceptors.remove(interceptor)) { this.size--; return true; } else { return false; } } @Nullable public ChannelInterceptor remove(int index) { ChannelInterceptor removed = this.interceptors.remove(index); if (removed != null) { this.size--; } return removed; } } }