Package org.springframework.xd.dirt.plugins

Source Code of org.springframework.xd.dirt.plugins.AbstractMessageBusBinderPlugin$TapLifecycleConnectionListener

/*
* Copyright 2013-2014 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.xd.dirt.plugins;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.utils.ThreadUtils;

import org.springframework.integration.channel.ChannelInterceptorAware;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.interceptor.WireTap;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.xd.dirt.integration.bus.MessageBus;
import org.springframework.xd.dirt.zookeeper.Paths;
import org.springframework.xd.dirt.zookeeper.ZooKeeperConnection;
import org.springframework.xd.dirt.zookeeper.ZooKeeperConnectionListener;
import org.springframework.xd.dirt.zookeeper.ZooKeeperUtils;
import org.springframework.xd.module.core.Module;
import org.springframework.xd.module.core.Plugin;


/**
* Abstract {@link Plugin} that has common implementation methods to bind/unbind {@link Module}'s message producers and
* consumers to/from {@link MessageBus}.
*
* @author Mark Fisher
* @author Gary Russell
* @author David Turanski
* @author Jennifer Hickey
* @author Glenn Renfro
* @author Ilayaperumal Gopinathan
*/
public abstract class AbstractMessageBusBinderPlugin extends AbstractPlugin {

  protected static final String MODULE_INPUT_CHANNEL = "input";

  protected static final String MODULE_OUTPUT_CHANNEL = "output";

  protected static final String TAP_CHANNEL_PREFIX = "tap:";

  protected static final String TOPIC_CHANNEL_PREFIX = "topic:";

  protected static final String JOB_CHANNEL_PREFIX = "job:";

  protected final MessageBus messageBus;

  /**
   * Cache of children under the taps path.
   */
  private volatile PathChildrenCache taps;

  /**
   * A {@link PathChildrenCacheListener} implementation that monitors tap additions and removals.
   */
  private final TapListener tapListener = new TapListener();

  /**
   * Map of channels that can be tapped. The keys are the tap channel names (e.g. tap:stream:ticktock.time.0),
   * and the values are the output channels from modules where the actual WireTap interceptors would be added.
   */
  private final Map<String, MessageChannel> tappableChannels = new HashMap<String, MessageChannel>();

  public AbstractMessageBusBinderPlugin(MessageBus messageBus) {
    this(messageBus, null);
  }

  public AbstractMessageBusBinderPlugin(MessageBus messageBus, ZooKeeperConnection zkConnection) {
    Assert.notNull(messageBus, "MessageBus must not be null.");
    this.messageBus = messageBus;
    if (zkConnection != null) {
      if (zkConnection.isConnected()) {
        startTapListener(zkConnection.getClient());
      }
      zkConnection.addListener(new TapLifecycleConnectionListener());
    }
  }

  private void startTapListener(CuratorFramework client) {
    String tapPath = Paths.build(Paths.TAPS);
    Paths.ensurePath(client, tapPath);
    taps = new PathChildrenCache(client, tapPath, true,
        ThreadUtils.newThreadFactory("TapsPathChildrenCache"));
    taps.getListenable().addListener(tapListener);
    try {
      taps.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
    }
    catch (Exception e) {
      throw ZooKeeperUtils.wrapThrowable(e, "failed to start TapListener");
    }
  }

  /**
   * Bind input/output channel of the module's message consumer/producers to {@link MessageBus}'s message
   * source/target entities.
   *
   * @param module the module whose consumer and producers to bind to the {@link MessageBus}.
   */
  protected final void bindConsumerAndProducers(Module module) {
    Properties[] properties = extractConsumerProducerProperties(module);
    MessageChannel inputChannel = module.getComponent(MODULE_INPUT_CHANNEL, MessageChannel.class);
    if (inputChannel != null) {
      bindMessageConsumer(inputChannel, getInputChannelName(module), properties[0]);
    }
    MessageChannel outputChannel = module.getComponent(MODULE_OUTPUT_CHANNEL, MessageChannel.class);
    if (outputChannel != null) {
      bindMessageProducer(outputChannel, getOutputChannelName(module), properties[1]);
      String tapChannelName = buildTapChannelName(module);
      tappableChannels.put(tapChannelName, outputChannel);
      if (isTapActive(tapChannelName)) {
        createAndBindTapChannel(tapChannelName, outputChannel);
      }
    }
  }

  protected final Properties[] extractConsumerProducerProperties(Module module) {
    Properties consumerProperties = new Properties();
    Properties producerProperties = new Properties();
    String consumerKeyPrefix = "consumer.";
    String producerKeyPrefix = "producer.";
    if (module.getDeploymentProperties() != null) {
      for (Map.Entry<String, String> entry : module.getDeploymentProperties().entrySet()) {
        if (entry.getKey().startsWith(consumerKeyPrefix)) {
          consumerProperties.put(entry.getKey().substring(consumerKeyPrefix.length()), entry.getValue());
        }
        else if (entry.getKey().startsWith(producerKeyPrefix)) {
          producerProperties.put(entry.getKey().substring(producerKeyPrefix.length()), entry.getValue());
        }
      }
    }
    return new Properties[] { consumerProperties, producerProperties };
  }

  @Override
  public void beforeShutdown(Module module) {
    unbindConsumer(module);
  }

  @Override
  public void removeModule(Module module) {
    super.removeModule(module);
    unbindProducers(module);
  }

  protected abstract String getInputChannelName(Module module);

  protected abstract String getOutputChannelName(Module module);

  protected abstract String buildTapChannelName(Module module);

  private void bindMessageConsumer(MessageChannel inputChannel, String inputChannelName,
      Properties consumerProperties) {
    if (isChannelPubSub(inputChannelName)) {
      messageBus.bindPubSubConsumer(inputChannelName, inputChannel, consumerProperties);
    }
    else {
      messageBus.bindConsumer(inputChannelName, inputChannel, consumerProperties);
    }
  }

  private void bindMessageProducer(MessageChannel outputChannel, String outputChannelName,
      Properties producerProperties) {
    if (isChannelPubSub(outputChannelName)) {
      messageBus.bindPubSubProducer(outputChannelName, outputChannel, producerProperties);
    }
    else {
      messageBus.bindProducer(outputChannelName, outputChannel, producerProperties);
    }
  }

  /**
   * Creates a wiretap on the output channel of the {@link Module} and binds the tap channel to {@link MessageBus}'s
   * message target.
   *
   * @param tapChannelName the name of the tap channel
   * @param outputChannel the channel to tap
   */
  private void createAndBindTapChannel(String tapChannelName, MessageChannel outputChannel) {
    logger.info("creating and binding tap channel for {}", tapChannelName);
    if (outputChannel instanceof ChannelInterceptorAware) {
      MessageChannel tapChannel = tapOutputChannel(tapChannelName, (ChannelInterceptorAware) outputChannel);
      messageBus.bindPubSubProducer(tapChannelName, tapChannel, null); // TODO tap producer props
    }
    else {
      if (logger.isDebugEnabled()) {
        logger.debug("output channel is not interceptor aware. Tap will not be created.");
      }
    }
  }

  private MessageChannel tapOutputChannel(String tapChannelName, ChannelInterceptorAware outputChannel) {
    DirectChannel tapChannel = new DirectChannel();
    tapChannel.setBeanName(tapChannelName + ".tap.bridge");
    outputChannel.addInterceptor(new WireTap(tapChannel));
    return tapChannel;
  }

  /**
   * Unbind the input channel of the module from the {@link MessageBus}
   * (stop sending new messages to the module so it can be stopped).
   *
   * @param module the module for which the consumer is to be unbound from the {@link MessageBus}.
   */
  protected void unbindConsumer(Module module) {
    MessageChannel inputChannel = module.getComponent(MODULE_INPUT_CHANNEL, MessageChannel.class);
    if (inputChannel != null) {
      messageBus.unbindConsumer(getInputChannelName(module), inputChannel);
      if (logger.isDebugEnabled()) {
        logger.debug("Unbound consumer for " + module.toString());
      }
    }
  }

  /**
   * Unbind the output channel of the module (and tap if present) from the {@link MessageBus}
   * (after it has been stopped).
   *
   * @param module the module for which producers are to be unbound from the {@link MessageBus}.
   */
  protected void unbindProducers(Module module) {
    MessageChannel outputChannel = module.getComponent(MODULE_OUTPUT_CHANNEL, MessageChannel.class);
    if (outputChannel != null) {
      messageBus.unbindProducer(getOutputChannelName(module), outputChannel);
      unbindTapChannel(buildTapChannelName(module));
      if (logger.isDebugEnabled()) {
        logger.debug("Unbound producer(s) for " + module.toString());
      }
    }
  }

  private void unbindTapChannel(String tapChannelName) {
    // Should this be unbindProducer() as there won't be multiple producers on the tap channel.
    MessageChannel tappedChannel = tappableChannels.remove(tapChannelName);
    if (tappedChannel instanceof ChannelInterceptorAware) {
      ChannelInterceptorAware interceptorAware = ((ChannelInterceptorAware) tappedChannel);
      List<ChannelInterceptor> interceptors = new ArrayList<ChannelInterceptor>();
      for (ChannelInterceptor interceptor : interceptorAware.getChannelInterceptors()) {
        if (interceptor instanceof WireTap) {
          ((WireTap) interceptor).stop();
        }
        else {
          interceptors.add(interceptor);
        }
      }
      interceptorAware.setInterceptors(interceptors);
      messageBus.unbindProducers(tapChannelName);
    }
  }

  private boolean isChannelPubSub(String channelName) {
    Assert.isTrue(StringUtils.hasText(channelName), "Channel name should not be empty/null.");
    // Check if the channelName starts with tap: or topic:
    return (channelName.startsWith(TAP_CHANNEL_PREFIX) || channelName.startsWith(TOPIC_CHANNEL_PREFIX));
  }

  @Override
  public int getOrder() {
    return 0;
  }


  /**
   * Event handler for tap additions.
   *
   * @param client curator client
   * @param data module data
   */
  private void onTapAdded(CuratorFramework client, ChildData data) {
    String tapChannelName = buildTapChannelNameFromPath(data.getPath());
    MessageChannel outputChannel = tappableChannels.get(tapChannelName);
    if (outputChannel != null) {
      createAndBindTapChannel(tapChannelName, outputChannel);
    }
  }

  /**
   * Event handler for tap removals.
   *
   * @param client curator client
   * @param data module data
   */
  private void onTapRemoved(CuratorFramework client, ChildData data) {
    unbindTapChannel(buildTapChannelNameFromPath(data.getPath()));
  }

  /**
   * Checks whether the provided tap channel name has one or more active subscribers.
   *
   * @param tapChannelName the tap channel to check
   *
   * @return {@code true} if the tap does have one or more active subscribers
   */
  private boolean isTapActive(String tapChannelName) {
    Assert.state(taps != null, "tap cache not started");
    List<ChildData> currentTaps = taps.getCurrentData();
    for (ChildData data : currentTaps) {
      // example path: /taps/stream:ticktock.time.0
      if (buildTapChannelNameFromPath(data.getPath()).equals(tapChannelName)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Generates the name of a tap channel given a ZooKeeper tap path.
   *
   * @param path the ZooKeeper path under {@link Paths#TAPS}.
   *
   * @return the tap channel name
   */
  private String buildTapChannelNameFromPath(String path) {
    return TAP_CHANNEL_PREFIX + Paths.stripPath(path);
  }


  /**
   * Listener for tap additions and removals under {@link Paths#TAPS}.
   */
  class TapListener implements PathChildrenCacheListener {

    /**
     * {@inheritDoc}
     */
    @Override
    public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
      ZooKeeperUtils.logCacheEvent(logger, event);
      switch (event.getType()) {
        case INITIALIZED:
          break;
        case CHILD_ADDED:
          onTapAdded(client, event.getData());
          break;
        case CHILD_REMOVED:
          onTapRemoved(client, event.getData());
          break;
        default:
          break;
      }
    }
  }


  /**
   * A {@link ZooKeeperConnectionListener} that manages the lifecycle of the taps cache listener.
   */
  class TapLifecycleConnectionListener implements ZooKeeperConnectionListener {

    @Override
    public void onDisconnect(CuratorFramework client) {
      taps.getListenable().removeListener(tapListener);
      try {
        taps.close();
      }
      catch (Exception e) {
        throw ZooKeeperUtils.wrapThrowable(e);
      }
    }

    @Override
    public void onSuspend(CuratorFramework client) {
    }

    @Override
    public void onConnect(CuratorFramework client) {
      startTapListener(client);
    }

    @Override
    public void onResume(CuratorFramework client) {
    }
  }

}
TOP

Related Classes of org.springframework.xd.dirt.plugins.AbstractMessageBusBinderPlugin$TapLifecycleConnectionListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.