com.alibaba.napoli.metamorphosis.client.producer.SimpleMessageProducer.java Source code

Java tutorial

Introduction

Here is the source code for com.alibaba.napoli.metamorphosis.client.producer.SimpleMessageProducer.java

Source

/*
 * (C) 2007-2012 Alibaba Group Holding Limited.
 * 
 * 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.
 * Authors:
 *   wuhua <wq163@163.com> , boyan <killme2008@gmail.com>
 */
package com.alibaba.napoli.metamorphosis.client.producer;

import java.nio.ByteBuffer;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Pattern;

import javax.transaction.xa.XAException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.alibaba.napoli.metamorphosis.client.MetaMessageSessionFactory;
import com.alibaba.napoli.metamorphosis.client.RemotingClientWrapper;
import com.alibaba.napoli.metamorphosis.client.transaction.TransactionContext;
import com.alibaba.napoli.metamorphosis.client.transaction.TransactionSession;
import com.alibaba.napoli.gecko.core.command.ResponseCommand;
import com.alibaba.napoli.gecko.core.util.OpaqueGenerator;
import com.alibaba.napoli.gecko.service.Connection;
import com.alibaba.napoli.gecko.service.SingleRequestCallBackListener;
import com.alibaba.napoli.gecko.service.exception.NotifyRemotingException;
import com.alibaba.napoli.metamorphosis.Message;
import com.alibaba.napoli.metamorphosis.MessageAccessor;
import com.alibaba.napoli.metamorphosis.cluster.Partition;
import com.alibaba.napoli.metamorphosis.exception.InvalidMessageException;
import com.alibaba.napoli.metamorphosis.exception.MetaClientException;
import com.alibaba.napoli.metamorphosis.exception.MetaOpeartionTimeoutException;
import com.alibaba.napoli.metamorphosis.exception.TransactionInProgressException;
import com.alibaba.napoli.metamorphosis.network.BooleanCommand;
import com.alibaba.napoli.metamorphosis.network.ByteUtils;
import com.alibaba.napoli.metamorphosis.network.HttpStatus;
import com.alibaba.napoli.metamorphosis.network.PutCommand;
import com.alibaba.napoli.metamorphosis.transaction.TransactionId;
import com.alibaba.napoli.metamorphosis.utils.LongSequenceGenerator;
import com.alibaba.napoli.metamorphosis.utils.MessageFlagUtils;
import com.alibaba.napoli.metamorphosis.utils.MetaStatLog;
import com.alibaba.napoli.metamorphosis.utils.MetaZookeeperHelper;
import com.alibaba.napoli.metamorphosis.utils.StatConstants;

/**
 * 
 * 
 * @author boyan
 * @Date 2011-4-21
 * @author wuhua
 * @Date 2011-8-3
 */
public class SimpleMessageProducer implements MessageProducer, TransactionSession {
    private static final Log log = LogFactory.getLog(SimpleMessageProducer.class);
    protected static final long DEFAULT_OP_TIMEOUT = 3000L;
    private static final int TIMEOUT_THRESHOLD = Integer
            .parseInt(System.getProperty("meta.send.timeout.threshold", "200"));
    private final MetaMessageSessionFactory messageSessionFactory;
    protected final RemotingClientWrapper remotingClient;
    protected final PartitionSelector partitionSelector;
    protected final ProducerZooKeeper producerZooKeeper;
    protected final String sessionId;
    // 10
    protected volatile int transactionTimeout = 0;
    // private final boolean ordered;
    private volatile boolean shutdown;
    private static final int MAX_RETRY = 1;
    private final LongSequenceGenerator localTxIdGenerator;

    public SimpleMessageProducer(final MetaMessageSessionFactory messageSessionFactory,
            final RemotingClientWrapper remotingClient, final PartitionSelector partitionSelector,
            final ProducerZooKeeper producerZooKeeper, final String sessionId) {
        super();
        this.sessionId = sessionId;
        this.messageSessionFactory = messageSessionFactory;
        this.remotingClient = remotingClient;
        this.partitionSelector = partitionSelector;
        this.producerZooKeeper = producerZooKeeper;
        this.localTxIdGenerator = new LongSequenceGenerator();

        // this.ordered = ordered;
    }

    @Override
    public PartitionSelector getPartitionSelector() {
        return this.partitionSelector;
    }

    @Override
    @Deprecated
    public boolean isOrdered() {
        return false;
    }

    @Override
    public void publish(final String topic) {
        this.checkState();
        this.checkTopic(topic);
        this.producerZooKeeper.publishTopic(topic);
        // this.localMessageStorageManager.setMessageRecoverer(this.recoverer);
    }

    @Override
    public void setDefaultTopic(final String topic) {
        this.producerZooKeeper.setDefaultTopic(topic);
    }

    private void checkTopic(final String topic) {
        if (StringUtils.isBlank(topic)) {
            throw new IllegalArgumentException("Blank topic:" + topic);
        }
    }

    static final Pattern RESULT_SPLITER = Pattern.compile(" ");

    @Override
    public SendResult sendMessage(final Message message, final long timeout, final TimeUnit unit)
            throws MetaClientException, InterruptedException {
        this.checkState();
        this.checkMessage(message);
        return this.sendMessageToServer(message, timeout, unit);
    }

    /** ???? */
    protected SendResult sendMessageToServer(final Message message, final long timeout, final TimeUnit unit)
            throws MetaClientException, InterruptedException, MetaOpeartionTimeoutException {
        /**
         * ??
         * <ul>
         * <li>??MetaClientExceptioncheck</li>
         * <li>??SendResult?</li>
         * </ul>
         */
        // this.checkState();
        // this.checkMessage(message);
        SendResult result = null;
        final long start = System.currentTimeMillis();
        int retry = 0;
        final long timeoutInMills = TimeUnit.MILLISECONDS.convert(timeout, unit);
        final byte[] data = SimpleMessageProducer.encodeData(message);
        try {
            for (int i = 0; i < MAX_RETRY; i++) {
                result = this.send0(message, data, timeout, unit);
                if (result.isSuccess()) {
                    break;
                }
                if (System.currentTimeMillis() - start >= timeoutInMills) {
                    throw new MetaOpeartionTimeoutException("Send message timeout in " + timeoutInMills + " mills");
                }
                retry++;
            }
        } finally {
            final long duration = System.currentTimeMillis() - start;
            MetaStatLog.addStatValue2(null, StatConstants.PUT_TIME_STAT, message.getTopic(), duration);
            if (duration > TIMEOUT_THRESHOLD) {
                MetaStatLog.addStatValue2(null, StatConstants.PUT_TIMEOUT_STAT, message.getTopic(), duration);
            }
            if (retry > 0) {
                MetaStatLog.addStatValue2(null, StatConstants.PUT_RETRY_STAT, message.getTopic(), retry);
            }
        }
        return result;
    }

    private Partition selectPartition(final Message message) throws MetaClientException {
        return this.producerZooKeeper.selectPartition(message.getTopic(), message, this.partitionSelector);
    }

    /**
     * ?
     * 
     * @author boyan
     * @date 2011-08-17
     */
    protected final ThreadLocal<LastSentInfo> lastSentInfo = new ThreadLocal<LastSentInfo>();

    /**
     * ?
     */
    final protected ThreadLocal<TransactionContext> transactionContext = new ThreadLocal<TransactionContext>();

    // ????????serverUrl
    static class LastSentInfo {
        final String serverUrl;

        public LastSentInfo(final String serverUrl) {
            super();
            this.serverUrl = serverUrl;
        }

    }

    private TransactionContext getTx() throws MetaClientException {
        final TransactionContext ctx = this.transactionContext.get();
        if (ctx == null) {
            throw new MetaClientException("There is no transaction begun");
        }
        return ctx;
    }

    @Override
    public void removeContext(final TransactionContext ctx) {
        this.transactionContext.remove();
        this.resetLastSentInfo();
    }

    @Override
    public String getSessionId() {
        return this.sessionId;
    }

    @Override
    public void setTransactionTimeout(final int seconds) throws MetaClientException {
        if (seconds < 0) {
            throw new IllegalArgumentException("Illegal transaction timeout value");
        }
        this.transactionTimeout = seconds;

    }

    @Override
    public int getTransactionTimeout() throws MetaClientException {
        return this.transactionTimeout;
    }

    /**
     * ???
     * 
     * @throws MetaClientException
     *             ?TransactionInProgressException
     */
    @Override
    public void beginTransaction() throws MetaClientException {
        // begin?????
        TransactionContext ctx = this.transactionContext.get();
        if (ctx == null) {
            ctx = new TransactionContext(this.remotingClient, null, this, this.localTxIdGenerator,
                    this.transactionTimeout);
            this.transactionContext.set(ctx);
        } else {
            throw new TransactionInProgressException("A transaction has begun");
        }

    }

    /**
     * ???
     * 
     * @param serverUrl
     * @throws MetaClientException
     */
    protected void beforeSendMessageFirstTime(final String serverUrl) throws MetaClientException, XAException {
        final TransactionContext tx = this.getTx();
        // ?serverUrlbegin
        if (tx.getTransactionId() == null) {
            tx.setServerUrl(serverUrl);
            tx.begin();
        }
    }

    /**
     * ?
     * 
     * @param serverUrl
     */
    protected void logLastSentInfo(final String serverUrl) {
        if (this.isInTransaction() && this.lastSentInfo.get() == null) {
            this.lastSentInfo.set(new LastSentInfo(serverUrl));
        }
    }

    /**
     * id
     * 
     * @return
     * @throws MetaClientException
     */
    protected TransactionId getTransactionId() throws MetaClientException {
        if (this.isInTransaction()) {
            return this.getTx().getTransactionId();
        } else {
            return null;
        }
    }

    /**
     * ?
     * 
     * @return
     */
    protected boolean isInTransaction() {
        return this.transactionContext.get() != null;
    }

    /**
     * ??????beginTransaction?
     * 
     * @see #beginTransaction()
     * @throws MetaClientException
     */
    @Override
    public void commit() throws MetaClientException {
        try {
            final TransactionContext ctx = this.getTx();
            ctx.commit();
        } finally {
            this.resetLastSentInfo();
            this.transactionContext.remove();
        }
    }

    /**
     * ???beginTransaction?
     * 
     * @throws MetaClientException
     * @see #beginTransaction()
     */
    @Override
    public void rollback() throws MetaClientException {
        try {
            final TransactionContext ctx = this.getTx();
            ctx.rollback();
        } finally {
            this.resetLastSentInfo();
            this.transactionContext.remove();
        }
    }

    protected void resetLastSentInfo() {
        this.lastSentInfo.remove();
    }

    private SendResult send0(final Message message, final byte[] encodedData, final long timeout,
            final TimeUnit unit) throws InterruptedException, MetaClientException {
        try {
            final String topic = message.getTopic();
            Partition partition = null;
            String serverUrl = null;
            // ???broker
            if (this.isInTransaction()) {
                final LastSentInfo info = this.lastSentInfo.get();
                if (info != null) {
                    serverUrl = info.serverUrl;
                    // broker?
                    partition = this.producerZooKeeper.selectPartition(topic, message, this.partitionSelector,
                            serverUrl);
                    if (partition == null) {
                        // ?
                        throw new MetaClientException("There is no partitions in `" + serverUrl
                                + "` to send message with topic `" + topic + "` in a transaction");
                    }
                }
            }
            if (partition == null) {
                partition = this.selectPartition(message);
            }
            if (partition == null) {
                throw new MetaClientException("There is no aviable partition for topic " + topic
                        + ",maybe you don't publish it at first?");
            }
            if (serverUrl == null) {
                serverUrl = this.producerZooKeeper.selectBroker(topic, partition);
            }
            if (serverUrl == null) {
                throw new MetaClientException("There is no aviable server right now for topic " + topic
                        + " and partition " + partition + ",maybe you don't publish it at first?");
            }

            if (this.isInTransaction() && this.lastSentInfo.get() == null) {
                // ????
                this.beforeSendMessageFirstTime(serverUrl);
            }

            final int flag = MessageFlagUtils.getFlag(message);
            final PutCommand putCommand = new PutCommand(topic, partition.getPartition(), encodedData,
                    this.getTransactionId(), flag, OpaqueGenerator.getNextOpaque());
            final BooleanCommand resp = this.invokeToGroup(serverUrl, partition, putCommand, message, timeout,
                    unit);
            return this.genSendResult(message, partition, serverUrl, resp);
        } catch (final TimeoutException e) {
            throw new MetaOpeartionTimeoutException(
                    "Send message timeout in " + TimeUnit.MILLISECONDS.convert(timeout, unit) + " mills");
        } catch (final InterruptedException e) {
            throw e;
        } catch (final Exception e) {
            throw new MetaClientException("send message failed", e);
        }
    }

    private SendResult genSendResult(final Message message, final Partition partition, final String serverUrl,
            final BooleanCommand resp) {
        final String resultStr = resp.getErrorMsg();

        switch (resp.getCode()) {
        case HttpStatus.Success: {
            // messageId partition offset
            final String[] tmps = RESULT_SPLITER.split(resultStr);
            // ??id?id?
            MessageAccessor.setId(message, Long.parseLong(tmps[1]));
            final Partition serverPart = new Partition(Integer.parseInt(tmps[2]), Integer.parseInt(tmps[3]));
            MessageAccessor.setPartition(message, serverPart);
            // ????
            this.logLastSentInfo(serverUrl);
            return new SendResult(true, serverPart, Long.parseLong(tmps[4]), resultStr);
        }
        case HttpStatus.Forbidden: {
            if (log.isDebugEnabled()) {
                log.debug(resultStr);
            }
            return new SendResult(false, null, -1, String.valueOf(HttpStatus.Forbidden));
        }
        default:
            return new SendResult(false, null, -1, resultStr);
        }
    }

    protected BooleanCommand invokeToGroup(final String serverUrl, final Partition partition,
            final PutCommand putCommand, final Message message, final long timeout, final TimeUnit unit)
            throws InterruptedException, TimeoutException, NotifyRemotingException {

        return (BooleanCommand) this.remotingClient.invokeToGroup(serverUrl, putCommand, timeout, unit);
    }

    protected void checkState() {
        if (this.shutdown) {
            throw new IllegalStateException("Producer has been shutdown");
        }
    }

    /**
     * ??payload</br></br> 01attribute + payload
     * 
     * @param message
     * @return
     */
    public static byte[] encodeData(final Message message) {
        final byte[] payload = message.getData();
        final String attribute = message.getAttribute();
        byte[] attrData = null;
        if (attribute != null) {
            attrData = ByteUtils.getBytes(attribute);
        } else {
            return payload;
        }
        // attributenull
        final int attrLen = attrData == null ? 0 : attrData.length;
        final ByteBuffer buffer = ByteBuffer.allocate(4 + attrLen + payload.length);
        if (attribute != null) {
            buffer.putInt(attrLen);
            if (attrData != null) {
                buffer.put(attrData);
            }
        }

        buffer.put(payload);
        return buffer.array();
    }

    protected void checkMessage(final Message message) throws MetaClientException {
        if (message == null) {
            throw new InvalidMessageException("Null message");
        }
        if (StringUtils.isBlank(message.getTopic())) {
            throw new InvalidMessageException("Blank topic");
        }
        if (message.getData() == null) {
            throw new InvalidMessageException("Null data");
        }
    }

    @Override
    public void sendMessage(final Message message, final SendMessageCallback cb, final long time,
            final TimeUnit unit) {
        try {
            final String topic = message.getTopic();
            final Partition partition = this.selectPartition(message);
            if (partition == null) {
                throw new MetaClientException("There is no aviable partition for topic " + topic
                        + ",maybe you don't publish it at first?");
            }
            final String serverUrl = this.producerZooKeeper.selectBroker(topic, partition);
            if (serverUrl == null) {
                throw new MetaClientException("There is no aviable server right now for topic " + topic
                        + " and partition " + partition + ",maybe you don't publish it at first?");
            }

            final int flag = MessageFlagUtils.getFlag(message);
            final byte[] encodedData = SimpleMessageProducer.encodeData(message);
            final PutCommand putCommand = new PutCommand(topic, partition.getPartition(), encodedData,
                    this.getTransactionId(), flag, OpaqueGenerator.getNextOpaque());
            this.remotingClient.sendToGroup(serverUrl, putCommand, new SingleRequestCallBackListener() {
                @Override
                public void onResponse(final ResponseCommand responseCommand, final Connection conn) {
                    final SendResult rt = SimpleMessageProducer.this.genSendResult(message, partition, serverUrl,
                            (BooleanCommand) responseCommand);
                    cb.onMessageSent(rt);
                }

                @Override
                public void onException(final Exception e) {
                    cb.onException(e);
                }

                @Override
                public ThreadPoolExecutor getExecutor() {
                    return null;
                }
            }, time, unit);

        } catch (final Throwable e) {
            cb.onException(e);
        }

    }

    @Override
    public void sendMessage(final Message message, final SendMessageCallback cb) {
        this.sendMessage(message, cb, DEFAULT_OP_TIMEOUT, TimeUnit.MILLISECONDS);
    }

    @Override
    public SendResult sendMessage(final Message message) throws MetaClientException, InterruptedException {
        return this.sendMessage(message, DEFAULT_OP_TIMEOUT, TimeUnit.MILLISECONDS);
    }

    @Override
    public synchronized void shutdown() throws MetaClientException {
        if (this.shutdown) {
            return;
        }
        this.shutdown = true;
        this.messageSessionFactory.removeChild(this);
    }

    @Override
    public boolean makeUrgentMessage(String messageContent) {
        if (StringUtils.isBlank(messageContent)) {
            return false;
        }
        try {
            String[] messageArray = StringUtils.split(messageContent);
            String messageId = messageArray[1];
            MetaZookeeperHelper helper = new MetaZookeeperHelper(producerZooKeeper.getMetaZookeeper());
            helper.addUrgentMessage(messageId, messageContent);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

}