package com.sissi.context.impl; import java.net.SocketAddress; import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.sissi.commons.Trace; import com.sissi.context.JID; import com.sissi.context.JIDContext; import com.sissi.context.JIDContextBuilder; import com.sissi.context.JIDContextParam; import com.sissi.context.Status; import com.sissi.context.StatusBuilder; import com.sissi.field.impl.BeanField; import com.sissi.pipeline.Output; import com.sissi.protocol.Element; import com.sissi.server.ha.Keepalive; import com.sissi.server.tls.StartTls; import com.sissi.ucenter.vcard.VCardContext; /** * 在线JIDContext * * @author kim 2013-11-19 */ public class OnlineContextBuilder implements JIDContextBuilder { private final Log log = LogFactory.getLog(this.getClass()); /** * Identify */ private final AtomicLong indexes = new AtomicLong(); private final int priority = 0; private final int pong = -1; private final StatusBuilder statusBuilder; private final VCardContext vCardContext; private final Keepalive keepalive; private final String lang; private final Output offline; private final String domain; private final int retry; /** * @param retry 身份验证最大重试次数 * @param lang 默认语言 * @param domain 默认域 * @param getout * @param offline * @param statusBuilder * @param vCardContext * @param serverHeart */ public OnlineContextBuilder(int retry, String lang, String domain, Output offline, StatusBuilder statusBuilder, VCardContext vCardContext, Keepalive keepalive) { super(); this.statusBuilder = statusBuilder; this.vCardContext = vCardContext; this.keepalive = keepalive; this.offline = offline; this.domain = domain; this.retry = retry; this.lang = lang; } @Override public JIDContext build(JID jid, JIDContextParam param) { UserContext context = new UserContext(param); // 绑定JIDContext相关的出席状态 context.statusCurrent = context.statusOnline = this.statusBuilder.build(context); return context; } private class UserContext implements JIDContext { private final long index = OnlineContextBuilder.this.indexes.incrementAndGet(); private final AtomicInteger ping = new AtomicInteger(OnlineContextBuilder.this.pong); private final AtomicLong idle = new AtomicLong(System.currentTimeMillis()); /** * 预关闭标记 */ private final AtomicBoolean prepareClose = new AtomicBoolean(); /** * 是否已出席 */ private final AtomicBoolean presence = new AtomicBoolean(); /** * 同步出席锁 */ private final ReentrantLock presenceLock = new ReentrantLock(); /** * 是否已验证身份 */ private final AtomicBoolean auth = new AtomicBoolean(); /** * 已身份验证次数(Retry) */ private final AtomicInteger retry = new AtomicInteger(); /** * 是否已绑定 */ private final AtomicBoolean binding = new AtomicBoolean(); private final JIDContextParam param; /** * 优先级 */ private int priority = OnlineContextBuilder.this.priority; /** * 默认使用离线JID(已连接未验证身份) */ private JID jid = OfflineJID.OFFLINE; private Status statusCurrent; private Status statusOnline; private Output outputOnline; private Output outputCurrent; private String domain; private String lang; public UserContext(JIDContextParam param) { super(); this.param = param; this.outputCurrent = this.outputOnline = param.find(JIDContextParam.KEY_OUTPUT, Output.class); } public long index() { return this.index; } public boolean binding() { return this.binding.get(); } public JIDContext bind() { this.binding.set(true); return this; } @Override public UserContext auth(boolean canAccess) { this.auth.set(canAccess); if (!canAccess) { this.retry.incrementAndGet(); } return this; } @Override public boolean auth() { return this.auth.get(); } public boolean authRetry() { return OnlineContextBuilder.this.retry >= this.retry.get(); } public UserContext jid(JID jid) { this.jid = jid; return this; } public JID jid() { return this.jid; } @Override public boolean encrypt() { return this.param.find(JIDContextParam.KEY_STARTTLS, StartTls.class).startTls(this.domain()); } public boolean encrypted() { return this.param.find(JIDContextParam.KEY_STARTTLS, StartTls.class).isTls(this.domain()); } public JIDContext online() { try { this.presenceLock.lock(); // Exchange this.outputCurrent = this.outputOnline; this.statusCurrent = this.statusOnline; this.presence.set(true); return this; } finally { this.presenceLock.unlock(); } } public boolean onlined() { return this.presence.get(); } public JIDContext offline() { try { this.presenceLock.lock(); // Exchange OnlineContextBuilder.this.vCardContext.push(this.jid(), new BeanField<String>().name(VCardContext.FIELD_LOGOUT).value(String.valueOf(System.currentTimeMillis()))); this.outputCurrent = OnlineContextBuilder.this.offline; this.statusCurrent = OfflineStatus.STATUS; this.presence.set(false); return this; } finally { this.presenceLock.unlock(); } } /* * 优先级(-127-127) * * @see com.sissi.context.JIDContext#priority(int) */ @Override public JIDContext priority(int priority) { this.priority = priority < -128 ? -128 : priority > 127 ? 127 : priority; return this; } @Override public int priority() { return this.priority; } public long idle() { return this.idle.get(); } @Override public Status status() { return this.statusCurrent; } @Override public SocketAddress address() { return this.param.find(JIDContextParam.KEY_ADDRESS, SocketAddress.class); } public JIDContext lang(String lang) { this.lang = lang; return this; } public String lang() { return this.lang != null ? this.lang : OnlineContextBuilder.this.lang; } public JIDContext domain(String domain) { this.jid.domain(this.domain = domain); return this; } public String domain() { return this.domain != null ? this.domain : OnlineContextBuilder.this.domain; } public JIDContext reset() { this.priority = OnlineContextBuilder.this.priority; this.lang = OnlineContextBuilder.this.lang; this.ping.set(OnlineContextBuilder.this.pong); this.prepareClose.set(false); this.presence.set(false); this.retry.set(0); return this; } @Override public boolean close() { try { if (this.closePrepare()) { // 清除出席状态,关闭输出流 this.statusOnline.clear(); this.outputOnline.close(); } return true; } catch (Exception e) { return this.logFailed(e); } } public boolean closeTimeout() { return this.ping.get() != pong ? this.close() : false; } /* * 预关闭,仅允许Input,禁止Output * * @see com.sissi.context.JIDContext#closePrepare() */ public boolean closePrepare() { try { if (this.prepareClose.compareAndSet(false, true)) { this.offline(); } return true; } catch (Exception e) { return this.logFailed(e); } } @Override public JIDContext ping() { OnlineContextBuilder.this.keepalive.ping(this); return this; } public JIDContext ping(int ping) { this.ping.set(ping); return this; } @Override public JIDContext pong(Element element) { // XMPP节存在ID且与内置PING.id相同则重置PING if (element.getId() != null && this.ping.get() == element.getId().hashCode()) { this.ping.set(OnlineContextBuilder.this.pong); } return this; } private JIDContext write(Element element, Output output, boolean bare) { try { // 忽略相同JID的消息回路 if (this.jid().same(element.getFrom())) { OnlineContextBuilder.this.log.debug("Loop write: " + this.jid.asString() + " on " + element.getClass()); return this; } // Binding -> 自动分配From output.output(this, this.binding() ? element.setTo(bare ? this.jid.asStringWithBare() : this.jid().asString()) : element); } finally { // 更新IDLE this.idle.set(System.currentTimeMillis()); } return this; } @Override public JIDContext write(Element element) { return this.write(element, false); } public JIDContext write(Element element, boolean force) { return this.write(element, force, false); } public JIDContext write(Element element, boolean force, boolean bare) { return this.write(element, force ? this.outputOnline : this.outputCurrent, bare); } public JIDContext write(Collection<Element> elements) { return this.write(elements, false); } public JIDContext write(Collection<Element> elements, boolean force) { return this.write(elements, force, false); } public JIDContext write(Collection<Element> elements, boolean force, boolean bare) { for (Element element : elements) { try { this.write(element, force, bare); } catch (Exception e) { this.logFailed(e); } } return this; } private boolean logFailed(Exception e) { OnlineContextBuilder.this.log.debug(e.toString()); Trace.trace(OnlineContextBuilder.this.log, e); return false; } } }