package com.taobao.metamorphosis.client.extension.spring; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.exception.ExceptionUtils; import org.springframework.beans.factory.DisposableBean; import com.taobao.gecko.core.util.StringUtils; import com.taobao.metamorphosis.Message; import com.taobao.metamorphosis.client.MessageSessionFactory; import com.taobao.metamorphosis.client.producer.MessageProducer; import com.taobao.metamorphosis.client.producer.SendMessageCallback; import com.taobao.metamorphosis.client.producer.SendResult; import com.taobao.metamorphosis.exception.MetaClientException; import com.taobao.metamorphosis.utils.ThreadUtils; /** * Helper class that simplifies synchronous MetaQ access code. * * @author dennis<killme2008@gmail.com> * */ public class MetaqTemplate implements DisposableBean { private MessageSessionFactory messageSessionFactory; private String defaultTopic; private MessageBodyConverter<?> messageBodyConverter; private boolean shareProducer = false; private volatile MessageProducer sharedProducer; /** * returns if share a message producer between topics.It's false by default. * * @return * @since 1.4.5 */ public boolean isShareProducer() { return this.shareProducer; } /** * If true, the template will share a message producer between topics.It's * false by default. * * @param producerPerTopic * @since 1.4.5 */ public void setShareProducer(boolean producerPerTopic) { this.shareProducer = producerPerTopic; } private final ConcurrentHashMap<String/* topic */, FutureTask<MessageProducer>> producers = new ConcurrentHashMap<String, FutureTask<MessageProducer>>(); /** * Returns the default topic for producers. * * @return * @since 1.4.5 */ public String getDefaultTopic() { return this.defaultTopic; } /** * Returns the message body converter.The default is an instance of * JavaSerializationMessageBodyConverter. * * @return */ public MessageBodyConverter<?> getMessageBodyConverter() { return this.messageBodyConverter; } /** * Set message body converter. * * @param messageBodyConverter * @since 1.4.5 */ public void setMessageBodyConverter(MessageBodyConverter<?> messageBodyConverter) { if (messageBodyConverter == null) { throw new IllegalArgumentException("Null messageBodyConverter"); } this.messageBodyConverter = messageBodyConverter; } /** * Set the default topic for producers. * * @param defaultTopic * @since 1.4.5 */ public void setDefaultTopic(String defaultTopic) { this.defaultTopic = defaultTopic; } /** * Returns the associated message session factory. * * @return * @since 1.4.5 */ public MessageSessionFactory getMessageSessionFactory() { return this.messageSessionFactory; } /** * Set message session factory fot this template. * * @param messageSessionFactory * @since 1.4.5 */ public void setMessageSessionFactory(MessageSessionFactory messageSessionFactory) { if (messageSessionFactory == null) { throw new IllegalArgumentException("Null messageSessionFactory"); } this.messageSessionFactory = messageSessionFactory; } /** * Returns or create a message producer for topic. * * @param topic * @return * @since 1.4.5 */ public MessageProducer getOrCreateProducer(final String topic) { if (!this.shareProducer) { FutureTask<MessageProducer> task = this.producers.get(topic); if (task == null) { task = new FutureTask<MessageProducer>(new Callable<MessageProducer>() { @Override public MessageProducer call() throws Exception { MessageProducer producer = MetaqTemplate.this.messageSessionFactory.createProducer(); producer.publish(topic); if (!StringUtils.isBlank(MetaqTemplate.this.defaultTopic)) { producer.setDefaultTopic(MetaqTemplate.this.defaultTopic); } return producer; } }); FutureTask<MessageProducer> oldTask = this.producers.putIfAbsent(topic, task); if (oldTask != null) { task = oldTask; } else { task.run(); } } try { MessageProducer producer = task.get(); return producer; } catch (ExecutionException e) { throw ThreadUtils.launderThrowable(e.getCause()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } else { if (this.sharedProducer == null) { synchronized (this) { if (this.sharedProducer == null) { this.sharedProducer = this.messageSessionFactory.createProducer(); if (!StringUtils.isBlank(this.defaultTopic)) { this.sharedProducer.setDefaultTopic(this.defaultTopic); } } } } this.sharedProducer.publish(topic); return this.sharedProducer; } throw new IllegalStateException("Could not create producer for topic '" + topic + "'"); } /** * Send message built by message builder.Returns the sent result. * * @param builder * @return * @throws InterruptedException * @since 1.4.5 */ public SendResult send(MessageBuilder builder, long timeout, TimeUnit unit) throws InterruptedException { Message msg = builder.build(this.messageBodyConverter); final String topic = msg.getTopic(); MessageProducer producer = this.getOrCreateProducer(topic); try { return producer.sendMessage(msg, timeout, unit); } catch (MetaClientException e) { return new SendResult(false, null, -1, ExceptionUtils.getFullStackTrace(e)); } } /** * Send message built by message builder.Returns the sent result. * * @param builder * @return * @throws InterruptedException * @since 1.4.5 */ public SendResult send(MessageBuilder builder) throws InterruptedException { Message msg = builder.build(this.messageBodyConverter); final String topic = msg.getTopic(); MessageProducer producer = this.getOrCreateProducer(topic); try { return producer.sendMessage(msg); } catch (MetaClientException e) { return new SendResult(false, null, -1, ExceptionUtils.getFullStackTrace(e)); } } /** * Send message asynchronously with callback. * * @param builder * @param cb * @param timeout * @param unit * @since 1.4.5 */ public void send(MessageBuilder builder, SendMessageCallback cb, long timeout, TimeUnit unit) { Message msg = builder.build(this.messageBodyConverter); final String topic = msg.getTopic(); MessageProducer producer = this.getOrCreateProducer(topic); producer.sendMessage(msg, cb, timeout, unit); } @Override public void destroy() throws Exception { if (this.sharedProducer != null) { this.sharedProducer.shutdown(); this.sharedProducer = null; } for (FutureTask<MessageProducer> task : this.producers.values()) { try { MessageProducer producer = task.get(5000, TimeUnit.MILLISECONDS); if (producer != null) { producer.shutdown(); } } catch (Exception e) { // ignore } } this.producers.clear(); } /** * Send message asynchronously with callback. * * @param builder * @param cb * @since 1.4.5 */ public void send(MessageBuilder builder, SendMessageCallback cb) { Message msg = builder.build(this.messageBodyConverter); final String topic = msg.getTopic(); MessageProducer producer = this.getOrCreateProducer(topic); producer.sendMessage(msg, cb); } }