All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gu.simplemq.ChannelDispatcher Maven / Gradle / Ivy

There is a newer version: 2.3.17
Show newest version
package gu.simplemq;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.*;

import com.alibaba.fastjson.JSONException;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import gu.simplemq.exceptions.SmqTypeException;
import gu.simplemq.exceptions.SmqUnsubscribeException;
import gu.simplemq.json.BaseJsonEncoder;
import gu.simplemq.utils.CommonUtils;
import gu.simplemq.utils.Synchronizer;
import gu.simplemq.utils.Synchronizer.ReadWriteSynchronizer;

/**
 * (消息)频道订阅对象({@link Channel})管理类,负责频道的注册/注销,订阅/取消,消息数据解析及分发

* NOTE:如果不设置线程池对象,消息分发{@link #dispatch(String, String)}将以单线程工作,
* 参见{@link #setExecutor(ExecutorService)} * @author guyadong * */ public class ChannelDispatcher implements ISubscriber,Constant { private BaseJsonEncoder encoder = BaseJsonEncoder.getEncoder(); private static class SingletonExecutor{ /** 默认单线程池对象 */ private static final ExecutorService DEFAULT_EXECUTOR = MoreExecutors.getExitingExecutorService( new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactoryBuilder().setNameFormat("channel-dispatcher-%d").build())); } private static class SingletonTimerExecutor{ /** 默认定时线程对象 */ private static final ScheduledExecutorService DEFAULT_TIMEREXECUTOR = MoreExecutors.getExitingScheduledExecutorService( new ScheduledThreadPoolExecutor( 1, new ThreadFactoryBuilder() .setNameFormat("channel-dispatcher-timer-%d") .build())); } /** 注册的频道对象 */ protected final LinkedHashMap> channelSubs = new LinkedHashMap>(); /** 订阅的频道名 */ private final Set subChannelSet= new LinkedHashSet(); private final Synchronizer sync = new ReadWriteSynchronizer(); /** 线程池对象,默认使用单线程池对象,确保不会丢失数据 */ private volatile ExecutorService executor; /** 定时任务线程池 */ private volatile ScheduledExecutorService timerExecutor; public ChannelDispatcher() { } public ChannelDispatcher(Channel...channels) { register(channels); } public ChannelDispatcher(Collection> channels) { this(null ==channels?null:channels.toArray(new Channel[0])); } /** * @see #registedOnlyAsSet(String...) */ public String[] registedOnly(String... channels) { return registedOnlyAsSet(channels).toArray(new String[0]); } /** * 返回{@code channels}指定的频道名中已经注册的频道名 * @param channels */ public HashSet registedOnlyAsSet(String... channels) { sync.beginRead(); try{ HashSet chSet = new HashSet(CommonUtils.cleanEmptyAsList(channels)); if (!chSet.isEmpty()){ chSet.retainAll(channelSubs.keySet()); } return chSet; }finally{ sync.endRead(); } } /** * 子类重写此方法,检查通道名是否合法 * @param name * @return name 否则抛出异常 * @throws SmqTypeException */ protected String check(String name) throws SmqTypeException{return name;} @Override public Set> register(Channel... channels) { sync.beginWrite(); try { HashSet> chSet = new HashSet>(CommonUtils.cleanNullAsList(channels)); for (Channel ch : chSet) { channelSubs.put(check(ch.name), ch); } subscribe(Channel.getChannelNames(chSet).toArray(new String[0])); return chSet; }finally{ sync.endWrite(); } } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void register(final Channelchannel, long duration, TimeUnit unit) { register(checkNotNull(channel,"channel is null")); if(duration > 0){ checkArgument(null != unit,"unit is null"); // 定时执行频道注销 final ScheduledFuture timeUnregisterTask = this.getTimerExecutor().schedule(new Runnable(){ @Override public void run() { logger.debug("unregister channel '{}' caused by timeout", channel.name); unregister(channel.name); }}, duration, unit); channel.addUnregistedListener(new IUnregistedListener(){ @Override public void apply(Channel channel) { // 当执行注销时取消定时任务 if(!timeUnregisterTask.isDone() && timeUnregisterTask.getDelay(TimeUnit.MILLISECONDS) >0){ timeUnregisterTask.cancel(false); logger.debug("time task cancelled"); } }}); } } @Override public Set unregister(String... channels) { sync.beginWrite(); try { HashSet chSet = new HashSet(CommonUtils.cleanEmptyAsList(channels)); // 这里要判断是否为空,因为unsubscribe方法对于空列表参数会清除所有订阅 if(!chSet.isEmpty()){ unsubscribe(chSet.toArray(new String[0])); for (String ch : chSet) { Channel channel = this.channelSubs.get(ch); if(null != channel){ this.channelSubs.remove(ch); channel.onUnregisted(); } } } return chSet; }finally{ sync.endWrite(); } } @SuppressWarnings("rawtypes") @Override public Set unregister(Channel... channels) { return unregister(Channel.getChannelNames(channels)); } @Override public Set unregister(final IMessageAdapter messageAdapter) { sync.beginWrite(); try { String[] chSet = Maps.filterValues(channelSubs, new Predicate>() { @Override public boolean apply(Channel input) { return input.getAdapter() == messageAdapter; } }).keySet().toArray(new String[0]); return unregister(chSet); } finally { sync.endWrite(); } } @SuppressWarnings("rawtypes") @Override public Channel getChannel(String channel) { sync.beginRead(); try{ return channelSubs.get(channel); }finally{ sync.endRead(); } } @SuppressWarnings("unchecked") @Override public void dispatch(final String channel, final String message) { final Channel ch = getChannel(channel); if(null != ch){ this.getExecutor().execute(new Runnable(){ @Override public void run() { try{ Object deserialized = encoder.fromJson(message,ch.type); SimplemqContext.setChannel(ch); ch.onSubscribe(deserialized); } catch (JSONException e) { logger.warn(e.getMessage()); }catch (SmqUnsubscribeException e) { if(e.unregister){ unregister(ch.name); }else{ unsubscribe(ch.name); } logger.info("unsubscribe channel: {}",ch.name); } finally{ SimplemqContext.context.remove(); } }}); }else{ logger.warn("unregistered channel: '{}'",channel); } } @Override public String[] subscribe(String... channels) { sync.beginWrite(); try{ if (null == channels || 0 == channels.length) channels = channelSubs.keySet().toArray(new String[0]); else { channels = registedOnly(channels); } this.subChannelSet.addAll(Arrays.asList(channels)); return channels; }finally{ sync.endWrite(); } } @Override public String[] unsubscribe(String... channels) { sync.beginWrite(); try{ if (null == channels || 0 == channels.length){ channels = this.getSubscribes(); this.subChannelSet.clear(); }else{ HashSet chSet = this.registedOnlyAsSet(channels); this.subChannelSet.removeAll(chSet); channels = chSet.toArray(new String[0]); } return channels; }finally{ sync.endWrite(); } } @Override public String[] getSubscribes(){ sync.beginRead(); try{ return this.subChannelSet.toArray(new String[0]); }finally{ sync.endRead(); } } /** * 设置线程池对象,如果不指定线程对象,消息分发{@link #dispatch(String, String)}将以单线程工作 * @param executor 线程池对象,不可为{@code null} * @return 当前对象 */ public ChannelDispatcher setExecutor(ExecutorService executor) { this.executor = checkNotNull(executor,"executor is null");; return this; } /** * 设置用于定时任务的线程池对象 * @param timerExecutor * @return 当前对象 */ public ChannelDispatcher setTimerExecutor(ScheduledExecutorService timerExecutor) { this.timerExecutor = checkNotNull(timerExecutor,"timerExecutor is null"); return this; } private ExecutorService getExecutor(){ // double checking if(executor == null){ synchronized (this) { if(executor == null){ executor = SingletonExecutor.DEFAULT_EXECUTOR; } } } return executor; } private ScheduledExecutorService getTimerExecutor(){ // double checking if(timerExecutor == null){ synchronized (this) { if(timerExecutor == null){ timerExecutor = SingletonTimerExecutor.DEFAULT_TIMEREXECUTOR; } } } return timerExecutor; } public Map> allRegisteredChannels() { return Maps.newHashMap(channelSubs); } }