/*
* (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.taobao.metamorphosis.client.producer;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import javax.transaction.xa.XAResource;
import org.apache.commons.lang.StringUtils;
import com.taobao.gecko.core.util.ConcurrentHashSet;
import com.taobao.metamorphosis.client.MetaMessageSessionFactory;
import com.taobao.metamorphosis.client.RemotingClientWrapper;
import com.taobao.metamorphosis.client.producer.ProducerZooKeeper.BrokerChangeListener;
import com.taobao.metamorphosis.client.transaction.TransactionContext;
import com.taobao.metamorphosis.exception.InvalidBrokerException;
import com.taobao.metamorphosis.exception.MetaClientException;
/**
* XA��Ϣ�����ߵ�ʵ����
*
* @author boyan
*
*/
public class SimpleXAMessageProducer extends SimpleMessageProducer implements XAMessageProducer, BrokerChangeListener {
private String uniqueQualifier = DEFAULT_UNIQUE_QUALIFIER_PREFIX + "-" + getLocalhostName();
private static final String OVERWRITE_HOSTNAME_SYSTEM_PROPERTY = "metaq.client.xaproducer.hostname";
public static String getLocalhostName() {
String property = System.getProperty(OVERWRITE_HOSTNAME_SYSTEM_PROPERTY);
if (property != null && property.trim().length() > 0) {
return property;
}
try {
return InetAddress.getLocalHost().getHostName();
}
catch (final UnknownHostException e) {
throw new RuntimeException("unable to retrieve localhost name");
}
}
public SimpleXAMessageProducer(final MetaMessageSessionFactory messageSessionFactory,
final RemotingClientWrapper remotingClient, final PartitionSelector partitionSelector,
final ProducerZooKeeper producerZooKeeper, final String sessionId) {
super(messageSessionFactory, remotingClient, partitionSelector, producerZooKeeper, sessionId);
}
final Set<String> publishedTopics = new ConcurrentHashSet<String>();
private final Random rand = new Random();
private volatile String[] urls;
@Override
public void publish(final String topic) {
super.publish(topic);
if (this.publishedTopics.add(topic)) {
// try to select a broker that contains those topics.
this.generateTransactionBrokerURLs();
}
}
@Override
public void brokersChanged(String topic) {
this.generateTransactionBrokerURLs();
}
private void generateTransactionBrokerURLs() {
final List<Set<String>> brokerUrls = new ArrayList<Set<String>>();
for (final String topic : this.publishedTopics) {
brokerUrls.add(this.producerZooKeeper.getServerUrlSetByTopic(topic));
// Listen for brokers changing.
this.producerZooKeeper.onBrokerChange(topic, this);
}
final Set<String> resultSet = intersect(brokerUrls);
if (resultSet.isEmpty()) {
throw new InvalidBrokerException("Could not select a common broker url for topics:" + this.publishedTopics);
}
String[] newUrls = resultSet.toArray(new String[resultSet.size()]);
Arrays.sort(newUrls);
// Set new urls array.
this.urls = newUrls;
}
private String selectTransactionBrokerURL() {
String[] copiedUrls = this.urls;
if (copiedUrls == null || copiedUrls.length == 0) {
throw new InvalidBrokerException("Could not select a common broker url for topics:" + this.publishedTopics);
}
return copiedUrls[this.rand.nextInt(copiedUrls.length)];
}
static <T> Set<T> intersect(final List<Set<T>> sets) {
if (sets == null || sets.size() == 0) {
return null;
}
Set<T> rt = sets.get(0);
for (int i = 1; i < sets.size(); i++) {
final Set<T> copy = new HashSet<T>(rt);
copy.retainAll(sets.get(i));
rt = copy;
}
return rt;
}
@Override
public String getUniqueQualifier() {
return this.uniqueQualifier;
}
@Override
public void setUniqueQualifier(String uniqueQualifier) {
this.checkUniqueQualifier(this.uniqueQualifier);
this.uniqueQualifier = uniqueQualifier;
}
@Override
public void setUniqueQualifierPrefix(String prefix) {
this.checkUniqueQualifier(prefix);
this.uniqueQualifier = prefix + "-" + getLocalhostName();
}
private void checkUniqueQualifier(String prefix) {
if (StringUtils.isBlank(prefix)) {
throw new IllegalArgumentException("Blank unique qualifier for SimpleXAMessageProducer");
}
if (StringUtils.containsAny(prefix, "\r\n\t: ")) {
throw new IllegalArgumentException(
"Invalid unique qualifier,it should not contains newline,':' or blank characters.");
}
}
@Override
public XAResource getXAResource() throws MetaClientException {
TransactionContext xares = this.transactionContext.get();
if (xares != null) {
return xares;
}
else {
this.beginTransaction();
xares = this.transactionContext.get();
// ��������ѡ����broker
String selectedServer = this.selectTransactionBrokerURL();
xares.setServerUrl(selectedServer);
xares.setUniqueQualifier(this.uniqueQualifier);
xares.setXareresourceURLs(this.urls);
// ָ�����͵�url
this.logLastSentInfo(selectedServer);
return xares;
}
}
@Override
public synchronized void shutdown() throws MetaClientException {
for (String topic : this.publishedTopics) {
this.producerZooKeeper.deregisterBrokerChangeListener(topic, this);
}
super.shutdown();
}
}