Java tutorial
/* ----------------------------------------------------------------------------- This source file is part of Cell Cloud. Copyright (c) 2009-2014 Cell Cloud Team (www.cellcloud.net) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----------------------------------------------------------------------------- */ package net.cellcloud.talk; import java.io.ByteArrayOutputStream; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ExecutorService; import net.cellcloud.common.Cryptology; import net.cellcloud.common.LogLevel; import net.cellcloud.common.Logger; import net.cellcloud.common.Message; import net.cellcloud.common.NonblockingAcceptor; import net.cellcloud.common.Packet; import net.cellcloud.common.Service; import net.cellcloud.common.Session; import net.cellcloud.core.Cellet; import net.cellcloud.core.CelletSandbox; import net.cellcloud.core.Endpoint; import net.cellcloud.core.Nucleus; import net.cellcloud.core.NucleusContext; import net.cellcloud.exception.InvalidException; import net.cellcloud.exception.SingletonException; import net.cellcloud.http.CookieSessionManager; import net.cellcloud.http.HttpCapsule; import net.cellcloud.http.HttpService; import net.cellcloud.http.HttpSession; import net.cellcloud.http.WebSocketManager; import net.cellcloud.http.WebSocketSession; import net.cellcloud.talk.dialect.ActionDialectFactory; import net.cellcloud.talk.dialect.ChunkDialectFactory; import net.cellcloud.talk.dialect.Dialect; import net.cellcloud.talk.dialect.DialectEnumerator; import net.cellcloud.talk.stuff.PrimitiveSerializer; import net.cellcloud.util.CachedQueueExecutor; import net.cellcloud.util.Utils; import org.json.JSONException; import org.json.JSONObject; /** ?? * * @author Jiangwei Xu */ public final class TalkService implements Service, SpeakerDelegate { private static TalkService instance = null; private int port; private int block; private int maxConnections; private long sessionTimeout; // ??? HTTP ? private boolean httpEnabled; private int httpPort; private int httpQueueSize; private CookieSessionManager httpSessionManager; private HttpSessionListener httpSessionListener; private long httpSessionTimeout; private WebSocketManager webSocketManager; private NonblockingAcceptor acceptor; private NucleusContext nucleusContext; private TalkAcceptorHandler talkHandler; // protected ExecutorService executor; /// Session private ConcurrentHashMap<Long, Certificate> unidentifiedSessions; /// Session Tag private ConcurrentHashMap<Long, String> sessionTagMap; /// Tag Session context private ConcurrentHashMap<String, TalkSessionContext> tagContexts; /// ? private ConcurrentHashMap<String, SuspendedTracker> suspendedTrackers; /// Tag private ConcurrentSkipListSet<String> tagList; // ??? Speaker private ConcurrentHashMap<String, Speaker> speakerMap; protected Vector<Speaker> speakers; // HTTP ?? Speaker private ConcurrentHashMap<String, HttpSpeaker> httpSpeakerMap; protected Vector<HttpSpeaker> httpSpeakers; private TalkServiceDaemon daemon; private ArrayList<TalkListener> listeners; private CelletCallbackListener callbackListener; /** * @throws SingletonException */ public TalkService(NucleusContext nucleusContext) throws SingletonException { if (null == TalkService.instance) { TalkService.instance = this; this.nucleusContext = nucleusContext; this.port = 7000; this.block = 16384; this.maxConnections = 1000; this.httpEnabled = true; this.httpPort = 7070; this.httpQueueSize = 1000; // 24 ? this.sessionTimeout = 24 * 60 * 60 * 1000; // 5 this.httpSessionTimeout = 5 * 60 * 1000; // this.executor = CachedQueueExecutor.newCachedQueueThreadPool(8); // DialectEnumerator.getInstance().addFactory(new ActionDialectFactory()); DialectEnumerator.getInstance().addFactory(new ChunkDialectFactory()); this.callbackListener = DialectEnumerator.getInstance(); } else { throw new SingletonException(TalkService.class.getName()); } } /** * ??? */ public static TalkService getInstance() { return TalkService.instance; } /** * ??? * @return ?? true? false */ @Override public boolean startup() { if (null == this.unidentifiedSessions) { this.unidentifiedSessions = new ConcurrentHashMap<Long, Certificate>(); } if (null == this.sessionTagMap) { this.sessionTagMap = new ConcurrentHashMap<Long, String>(); } if (null == this.tagContexts) { this.tagContexts = new ConcurrentHashMap<String, TalkSessionContext>(); } if (null == this.suspendedTrackers) { this.suspendedTrackers = new ConcurrentHashMap<String, SuspendedTracker>(); } if (null == this.tagList) { this.tagList = new ConcurrentSkipListSet<String>(); } if (null == this.acceptor) { // ? this.acceptor = new NonblockingAcceptor(); this.acceptor.setBlockSize(this.block); // byte[] head = { 0x20, 0x10, 0x11, 0x10 }; byte[] tail = { 0x19, 0x78, 0x10, 0x04 }; this.acceptor.defineDataMark(head, tail); // ? this.talkHandler = new TalkAcceptorHandler(this); this.acceptor.setHandler(this.talkHandler); } // this.acceptor.setMaxConnectNum(this.maxConnections); boolean succeeded = this.acceptor.bind(this.port); if (succeeded) { startDaemon(); } if (succeeded && this.httpEnabled) { // ? HTTP ? startHttpService(); } return succeeded; } /** ?? */ @Override public void shutdown() { if (null != this.acceptor) { this.acceptor.unbind(); } stopDaemon(); if (null != this.executor) { this.executor.shutdown(); } if (this.httpEnabled && null != HttpService.getInstance()) { HttpService.getInstance().removeCapsule(this.httpPort); } if (null != this.tagContexts) { // ? Iterator<Map.Entry<String, TalkSessionContext>> iter = this.tagContexts.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, TalkSessionContext> entry = iter.next(); TalkSessionContext ctx = entry.getValue(); for (Cellet cellet : ctx.getTracker().getCelletList()) { cellet.quitted(ctx.getTag()); } } this.sessionTagMap.clear(); this.tagContexts.clear(); this.tagList.clear(); } if (null != this.speakers) { for (Speaker speaker : this.speakers) { speaker.hangUp(); } this.speakers.clear(); } if (null != this.httpSpeakers) { for (HttpSpeaker speaker : this.httpSpeakers) { speaker.hangUp(); } this.httpSpeakers.clear(); } if (null != this.speakerMap) { this.speakerMap.clear(); } if (null != this.httpSpeakerMap) { this.httpSpeakerMap.clear(); } } /** ?? * @note startup ?? * @param port ??? */ public void setPort(int port) { if (null != this.acceptor && this.acceptor.isRunning()) { throw new InvalidException("Can't set the port in talk service after the start"); } this.port = port; } /** ?? * @return */ public int getPort() { return this.port; } /** * ??? * @param size */ public void setBlockSize(int size) { this.block = size; } /** * * @param num */ public void setMaxConnections(int num) { if (null != this.acceptor && this.acceptor.isRunning()) { throw new InvalidException("Can't set the max number of connections in talk service after the start"); } this.maxConnections = num; } /** ? HTTP ? */ public void httpEnabled(boolean enabled) { if (null != HttpService.getInstance() && HttpService.getInstance().hasCapsule(this.httpPort)) { throw new InvalidException("Can't set the http enabled in talk service after the start"); } this.httpEnabled = enabled; } /** HTTP ?? * @note startup ?? */ public void setHttpPort(int port) { if (null != this.acceptor && this.acceptor.isRunning()) { throw new InvalidException("Can't set the http port in talk service after the start"); } this.httpPort = port; } /** HTTP ?? * @return */ public int getHttpPort() { return this.httpPort; } /** HTTP ?? * @param value */ public void setHttpQueueSize(int value) { this.httpQueueSize = value; } /** HTTP ? */ public void settHttpSessionTimeout(long timeoutInMillisecond) { this.httpSessionTimeout = timeoutInMillisecond; } /** ? */ public void startDaemon() { if (null == this.daemon) { this.daemon = new TalkServiceDaemon(); } if (!this.daemon.running) this.daemon.start(); } /** */ public void stopDaemon() { if (null != this.daemon) { this.daemon.stopSpinning(); // while (this.daemon.running) { try { Thread.sleep(10); } catch (InterruptedException e) { Logger.log(TalkService.class, e, LogLevel.DEBUG); } } this.daemon = null; } } /** ?? */ public void addListener(TalkListener listener) { if (null == this.listeners) { this.listeners = new ArrayList<TalkListener>(); } synchronized (this.listeners) { if (!this.listeners.contains(listener)) { this.listeners.add(listener); } } } /** ?? */ public void removeListener(TalkListener listener) { if (null == this.listeners) { return; } synchronized (this.listeners) { this.listeners.remove(listener); } } /** * ?? * @param listener * @return */ public boolean hasListener(TalkListener listener) { if (null == this.listeners) { return false; } synchronized (this.listeners) { return this.listeners.contains(listener); } } /** * ?? Tag * @return */ public Set<String> getTalkerList() { return this.tagList; } /** Speaker */ public boolean notice(final String targetTag, final Primitive primitive, final Cellet cellet, final CelletSandbox sandbox) { // Cellet ? if (!Nucleus.getInstance().checkSandbox(cellet, sandbox)) { Logger.w(TalkService.class, "Illegal cellet : " + cellet.getFeature().getIdentifier()); return false; } TalkSessionContext context = this.tagContexts.get(targetTag); if (null == context) { if (Logger.isDebugLevel()) { Logger.w(TalkService.class, "Can't find target tag in context list : " + targetTag); } // ? this.tryOfferPrimitive(targetTag, cellet, primitive); // ?? false return false; } // ? if (this.tryOfferPrimitive(targetTag, cellet, primitive)) { // ?? false return false; } Message message = null; synchronized (context) { if (context.getTracker().hasCellet(cellet)) { if (null != this.callbackListener && primitive.isDialectal()) { boolean ret = this.callbackListener.doTalk(cellet, targetTag, primitive.getDialect()); if (!ret) { // ?? return true; } } Session session = context.getLastSession(); message = this.packetDialogue(cellet, primitive, (session instanceof WebSocketSession)); if (null != message) { session.write(message); } } } return (null != message); } /** Speaker */ public boolean notice(final String targetTag, final Dialect dialect, final Cellet cellet, final CelletSandbox sandbox) { Primitive primitive = dialect.translate(); if (null != primitive) { return this.notice(targetTag, primitive, cellet, sandbox); } return false; } /** Cellet ? * * @note Client */ public boolean call(List<String> identifiers, InetSocketAddress address) { return this.call(identifiers, address, null, false); } /** * Cellet ? * * @param identifier * @param address * @return * * @note Client */ public boolean call(List<String> identifiers, InetSocketAddress address, boolean http) { return this.call(identifiers, address, null, http); } /** * Cellet ? * * @param identifier * @param address * @param capacity * @return * * @note Client */ public boolean call(List<String> identifiers, InetSocketAddress address, TalkCapacity capacity) { return this.call(identifiers, address, capacity, false); } /** Cellet ? * * @note Client */ public synchronized boolean call(List<String> identifiers, InetSocketAddress address, TalkCapacity capacity, boolean http) { if (!http) { // ??? Speaker if (null == this.speakers) { this.speakers = new Vector<Speaker>(); this.speakerMap = new ConcurrentHashMap<String, Speaker>(); } for (String identifier : identifiers) { if (this.speakerMap.containsKey(identifier)) { // ? Cellet??? Call return false; } } // Speaker Speaker speaker = new Speaker(address, this, this.block, capacity); this.speakers.add(speaker); // FIXME 28/11/14 call Speaker ? Lost ? lost true false // ?? Speaker ? //speaker.reset(); for (String identifier : identifiers) { this.speakerMap.put(identifier, speaker); } // Call return speaker.call(identifiers); } else { // HTTP ?? Speaker // TODO if (null == this.httpSpeakers) { this.httpSpeakers = new Vector<HttpSpeaker>(); this.httpSpeakerMap = new ConcurrentHashMap<String, HttpSpeaker>(); } for (String identifier : identifiers) { if (this.httpSpeakerMap.containsKey(identifier)) { // ? Cellet??? Call return false; } } // HttpSpeaker HttpSpeaker speaker = new HttpSpeaker(address, this, 30); this.httpSpeakers.add(speaker); for (String identifier : identifiers) { this.httpSpeakerMap.put(identifier, speaker); } // Call return speaker.call(identifiers); } } /** Cellet * * @note Client */ public void suspend(final String identifier, final long duration) { if (null == this.speakerMap || !this.speakerMap.containsKey(identifier)) return; Speaker speaker = this.speakerMap.get(identifier); if (null != speaker) { speaker.suspend(duration); } } /** ?? Cellet * * @note Client */ public void resume(final String identifier, final long startTime) { if (null == this.speakerMap || !this.speakerMap.containsKey(identifier)) return; Speaker speaker = this.speakerMap.get(identifier); if (null != speaker) { speaker.resume(startTime); } } /** Cellet ? * * @note Client */ public void hangUp(List<String> identifiers) { if (null != this.speakerMap) { for (String identifier : identifiers) { if (this.speakerMap.containsKey(identifier)) { Speaker speaker = this.speakerMap.remove(identifier); for (String celletIdentifier : speaker.getIdentifiers()) { this.speakerMap.remove(celletIdentifier); } this.speakers.remove(speaker); speaker.hangUp(); } } } if (null != this.httpSpeakerMap) { for (String identifier : identifiers) { if (this.httpSpeakerMap.containsKey(identifier)) { HttpSpeaker speaker = this.httpSpeakerMap.remove(identifier); for (String celletIdentifier : speaker.getIdentifiers()) { this.httpSpeakerMap.remove(celletIdentifier); } this.httpSpeakers.remove(speaker); speaker.hangUp(); } } } } /** ? Cellet ?? * * @note Client */ public boolean talk(final String identifier, final Primitive primitive) { if (null != this.speakerMap) { Speaker speaker = this.speakerMap.get(identifier); if (null != speaker) { // Speak return speaker.speak(identifier, primitive); } } if (null != this.httpSpeakerMap) { HttpSpeaker speaker = this.httpSpeakerMap.get(identifier); if (null != speaker) { // Speak return speaker.speak(identifier, primitive); } } return false; } /** ? Cellet ?? * * @note Client */ public boolean talk(final String identifier, final Dialect dialect) { if (null == this.speakerMap && null == this.httpSpeakerMap) return false; Primitive primitive = dialect.translate(); if (null != primitive) { return this.talk(identifier, primitive); } return false; } /** ?? Cellet ? * * @note Client */ public boolean isCalled(final String identifier) { if (null != this.speakerMap) { Speaker speaker = this.speakerMap.get(identifier); if (null != speaker) { return speaker.isCalled(); } } if (null != this.httpSpeakerMap) { HttpSpeaker speaker = this.httpSpeakerMap.get(identifier); if (null != speaker) { return speaker.isCalled(); } } return false; } /** Cellet ??? * * @note Client */ public boolean isSuspended(final String identifier) { if (null == this.speakerMap) return false; Speaker speaker = this.speakerMap.get(identifier); if (null != speaker) { return speaker.isSuspended(); } return false; } public ExecutorService getExecutor() { return this.executor; } /** ? HTTP ? */ private void startHttpService() { if (null == HttpService.getInstance()) { Logger.w(TalkService.class, "Starts talk http service failed, http service is not started."); return; } this.webSocketManager = HttpService.getInstance().activeWebSocket(this.httpPort + 1, this.httpQueueSize, new WebSocketMessageHandler(this)); // Session ? this.httpSessionManager = new CookieSessionManager(); // ? this.httpSessionListener = new HttpSessionListener(); this.httpSessionManager.addSessionListener(this.httpSessionListener); // ? HttpCapsule capsule = new HttpCapsule(this.httpPort, this.httpQueueSize); // Session ? capsule.setSessionManager(this.httpSessionManager); // ? Holder capsule.addHolder(new HttpInterrogationHandler(this)); capsule.addHolder(new HttpCheckHandler(this)); capsule.addHolder(new HttpRequestHandler(this)); capsule.addHolder(new HttpDialogueHandler(this)); capsule.addHolder(new HttpHeartbeatHandler()); // HTTP ? HttpService.getInstance().addCapsule(capsule); } /** * Dialogue */ @Override public void onDialogue(Speakable speaker, String identifier, Primitive primitive) { if (null == this.listeners) { return; } synchronized (this.listeners) { for (TalkListener listener : this.listeners) { listener.dialogue(identifier, primitive); } } } /** * */ @Override public void onContacted(Speakable speaker, String identifier) { if (null == this.listeners) { return; } String tag = speaker.getRemoteTag(); synchronized (this.listeners) { for (TalkListener listener : this.listeners) { listener.contacted(identifier, tag); } } } /** * */ @Override public void onQuitted(Speakable speaker, String identifier) { if (null == this.listeners) { return; } String tag = speaker.getRemoteTag(); synchronized (this.listeners) { for (TalkListener listener : this.listeners) { listener.quitted(identifier, tag); } } } /** * */ @Override public void onSuspended(Speakable speaker, long timestamp, int mode) { if (null == this.listeners) { return; } String tag = speaker.getRemoteTag(); synchronized (this.listeners) { for (TalkListener listener : this.listeners) { listener.suspended(tag, timestamp, mode); } } } /** * ?? */ @Override public void onResumed(Speakable speaker, long timestamp, Primitive primitive) { if (null == this.listeners) { return; } String tag = speaker.getRemoteTag(); synchronized (this.listeners) { for (TalkListener listener : this.listeners) { listener.resumed(tag, timestamp, primitive); } } } /** * ? */ @Override public void onFailed(Speakable speaker, TalkServiceFailure failure) { if (null == this.listeners) { return; } String tag = speaker.getRemoteTag(); synchronized (this.listeners) { for (TalkListener listener : this.listeners) { listener.failed(tag, failure); } } } /** * Endpoint * @param remoteTag * @return */ public Endpoint findEndpoint(String remoteTag) { TalkSessionContext ctx = this.tagContexts.get(remoteTag); if (null != ctx) { return ctx.getEndpoint(); } return null; } /** ? Session */ protected synchronized Certificate openSession(Session session) { Long sid = session.getId(); if (this.unidentifiedSessions.containsKey(sid)) { return this.unidentifiedSessions.get(sid); } Certificate cert = new Certificate(); cert.session = session; cert.key = Utils.randomString(8); cert.plaintext = Utils.randomString(16); this.unidentifiedSessions.put(sid, cert); return cert; } /** Session */ protected synchronized void closeSession(final Session session) { String tag = this.sessionTagMap.get(session.getId()); if (null != tag) { TalkSessionContext ctx = this.tagContexts.get(tag); if (null != ctx) { TalkTracker tracker = ctx.getTracker(); // ?? if (tracker.isAutoSuspend()) { // this.suspendTalk(ctx, SuspendMode.PASSIVE); for (Cellet cellet : tracker.getCelletList()) { // Cellet cellet.suspended(tag); } } else if (this.suspendedTrackers.containsKey(tag)) { // ?? Cellet for (Cellet cellet : tracker.getCelletList()) { SuspendedTracker st = this.suspendedTrackers.get(tag); if (!st.exist(cellet)) { // cellet.quitted(tag); } else { // // FIXME 01/01/2013 ??? // ??? } } } else { // ? ctx.removeSession(session); if (ctx.numSessions() == 0) { for (Cellet cellet : tracker.getCelletList()) { cellet.quitted(tag); } Logger.i(this.getClass(), "Clear session: " + tag); // ? this.tagContexts.remove(tag); this.tagList.remove(tag); } } } this.sessionTagMap.remove(session.getId()); } else { // Logger.i(this.getClass(), "Can NOT find tag with session: " + session.getAddress().getHostString()); } // ?? this.unidentifiedSessions.remove(session.getId()); } /** ? Session */ protected synchronized void acceptSession(Session session, String tag) { Long sid = session.getId(); this.unidentifiedSessions.remove(sid); // Session -> Tag this.sessionTagMap.put(session.getId(), tag); // Tag -> Context TalkSessionContext ctx = this.tagContexts.get(tag); if (null != ctx) { // Tag session ctx.addSession(session); } else { // ctx = new TalkSessionContext(tag, session); ctx.dialogueTickTime = this.getTickTime(); this.tagContexts.put(tag, ctx); } // Tag if (!this.tagList.contains(tag)) { this.tagList.add(tag); } } /** ? Session */ protected synchronized void rejectSession(Session session) { Long sid = session.getId(); StringBuilder log = new StringBuilder(); log.append("Talk service reject session ("); log.append(sid); log.append("): "); log.append(session.getAddress().getAddress().getHostAddress()); log.append(":"); log.append(session.getAddress().getPort()); Logger.w(TalkService.class, log.toString()); log = null; this.unidentifiedSessions.remove(sid); // Tag context String tag = this.sessionTagMap.remove(sid); if (null != tag) { TalkSessionContext ctx = this.tagContexts.get(tag); if (null != ctx) { ctx.removeSession(session); } } if (!(session instanceof HttpSession) && !(session instanceof WebSocketSession)) { this.acceptor.close(session); } else if (session instanceof WebSocketSession) { this.webSocketManager.close((WebSocketSession) session); } } /** Cellet */ protected TalkTracker processRequest(Session session, String tag, String identifier) { TalkSessionContext ctx = this.tagContexts.get(tag); if (null == ctx) { return null; } final Cellet cellet = Nucleus.getInstance().getCellet(identifier, this.nucleusContext); TalkTracker tracker = null; if (null != cellet) { tracker = ctx.getTracker(); if (!tracker.hasCellet(cellet)) { tracker.addCellet(cellet); } // ??? Talk if (this.tryResumeTalk(tag, cellet, SuspendMode.PASSIVE, 0)) { // resumed cellet.resumed(tag); } else { // contacted cellet.contacted(tag); } } return tracker; } /** ??? */ protected TalkCapacity processConsult(Session session, String tag, TalkCapacity capacity) { TalkSessionContext ctx = this.tagContexts.get(tag); if (null == ctx) { return new TalkCapacity(false, 0); } TalkTracker tracker = ctx.getTracker(); // ?? tracker.setAutoSuspend(capacity.autoSuspend); // tracker.setSuspendDuration(capacity.suspendDuration); return new TalkCapacity(tracker.isAutoSuspend(), tracker.getSuspendDuration()); } /** ? Cellet */ protected void processDialogue(Session session, String speakerTag, String targetIdentifier, Primitive primitive) { TalkSessionContext ctx = this.tagContexts.get(speakerTag); if (null != ctx) { ctx.dialogueTickTime = this.getTickTime(); TalkTracker tracker = ctx.getTracker(); Cellet cellet = tracker.getCellet(targetIdentifier); if (null != cellet) { primitive.setCelletIdentifier(cellet.getFeature().getIdentifier()); primitive.setCellet(cellet); if (null != this.callbackListener && primitive.isDialectal()) { boolean ret = this.callbackListener.doDialogue(cellet, speakerTag, primitive.getDialect()); if (!ret) { // ? return; } } // Cellet cellet.dialogue(speakerTag, primitive); } } } /** ? */ protected boolean processSuspend(Session session, String speakerTag, long duration) { TalkSessionContext ctx = this.tagContexts.get(speakerTag); if (null == ctx) { return false; } TalkTracker talkTracker = ctx.getTracker(); // SuspendedTracker st = this.suspendTalk(ctx, SuspendMode.INITATIVE); if (null != st) { // st.liveDuration = duration; // Cellet ? for (Cellet cellet : talkTracker.getCelletList()) { cellet.suspended(speakerTag); } return true; } return false; } /** ??? */ protected void processResume(Session session, String speakerTag, long startTime) { TalkSessionContext ctx = this.tagContexts.get(session); if (null == ctx) { return; } TalkTracker talkTracker = ctx.getTracker(); if (talkTracker.getCelletList().isEmpty()) { return; } // ?? for (Cellet cellet : talkTracker.getCelletList()) { if (this.tryResumeTalk(speakerTag, cellet, SuspendMode.INITATIVE, startTime)) { // ?? cellet.resumed(speakerTag); } } } /** ??? */ protected void noticeResume(Cellet cellet, String targetTag, Queue<Long> timestampQueue, Queue<Primitive> primitiveQueue, long startTime) { TalkSessionContext context = this.tagContexts.get(targetTag); if (null == context) { if (Logger.isDebugLevel()) { Logger.d(TalkService.class, "Not find session by remote tag"); } return; } Message message = null; synchronized (context) { // ? TalkTracker tracker = context.getTracker(); // ?? Cellet if (tracker.getCellet(cellet.getFeature().getIdentifier()) == cellet) { Session session = context.getLastSession(); // ?? for (int i = 0, size = timestampQueue.size(); i < size; ++i) { Long timestamp = timestampQueue.poll(); Primitive primitive = primitiveQueue.poll(); if (timestamp.longValue() >= startTime) { message = this.packetResume(targetTag, timestamp, primitive); if (null != message) { session.write(message); } } } } } } /** Session ? */ protected Certificate getCertificate(Session session) { return this.unidentifiedSessions.get(session.getId()); } /** ? Session */ protected void processUnidentifiedSessions(long time) { if (null == this.unidentifiedSessions || this.unidentifiedSessions.isEmpty()) { return; } // Session ArrayList<Session> sessionList = null; Iterator<Map.Entry<Long, Certificate>> iter = this.unidentifiedSessions.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<Long, Certificate> e = iter.next(); Certificate cert = e.getValue(); if (false == cert.checked) { cert.checked = true; deliverChecking(cert.session, cert.plaintext, cert.key); } else { // 20 if (time - cert.time > 20000) { if (null == sessionList) { sessionList = new ArrayList<Session>(); } sessionList.add(cert.session); } } } if (null != sessionList) { // Session Iterator<Session> siter = sessionList.iterator(); while (siter.hasNext()) { Session session = siter.next(); StringBuilder log = new StringBuilder(); log.append("Talk service session timeout: "); log.append(session.getAddress().getAddress().getHostAddress()); log.append(":"); log.append(session.getAddress().getPort()); Logger.i(TalkService.class, log.toString()); log = null; // this.unidentifiedSessions.remove(session.getId()); if (session instanceof HttpSession) { // HTTP Session this.httpSessionManager.unmanage((HttpSession) session); } else if (session instanceof WebSocketSession) { // WebSocket Session this.webSocketManager.close((WebSocketSession) session); } else { // ??? Session this.acceptor.close(session); } } sessionList.clear(); sessionList = null; } } /** Session tick time */ protected void updateSessionHeartbeat(Session session) { String tag = this.sessionTagMap.get(session.getId()); if (null == tag) { return; } TalkSessionContext ctx = this.tagContexts.get(tag); if (null != ctx) { ctx.updateSessionHeartbeat(session, this.getTickTime()); if (Logger.isDebugLevel()) { Logger.d(this.getClass(), "Talk service heartbeat from " + session.getAddress().getAddress().getHostAddress() + ":" + session.getAddress().getPort()); } } } /** */ protected long getTickTime() { return this.daemon.getTickTime(); } /** * ?? * @param timeout */ protected void checkSessionHeartbeat() { if (null == this.tagContexts) { return; } LinkedList<Session> closeList = new LinkedList<Session>(); for (Map.Entry<String, TalkSessionContext> entry : this.tagContexts.entrySet()) { TalkSessionContext ctx = entry.getValue(); List<Session> sl = ctx.getSessions(); for (Session s : sl) { long time = ctx.getSessionHeartbeat(s); if (time == 0) { continue; } if (this.daemon.getTickTime() - time > this.sessionTimeout) { // Session closeList.add(s); Logger.d(this.getClass(), "Session timeout in heartbeat: " + s.getAddress().getHostString()); } } } for (Session session : closeList) { this.closeSession(session); } closeList.clear(); closeList = null; } /** HTTP Session */ protected void checkHttpSessionHeartbeat() { if (null == this.httpSessionManager) { return; } this.executor.execute(new Runnable() { @Override public void run() { long time = daemon.getTickTime(); List<HttpSession> list = httpSessionManager.getSessions(); for (HttpSession session : list) { if (time - session.getHeartbeat() > httpSessionTimeout) { httpSessionManager.unmanage(session); } } } }); } /** ? */ protected void checkAndDeleteSuspendedTalk() { if (null == this.suspendedTrackers) { return; } // ???? // 1?? // 2??? Cellet ? // ? Iterator<Map.Entry<String, SuspendedTracker>> eiter = this.suspendedTrackers.entrySet().iterator(); while (eiter.hasNext()) { Map.Entry<String, SuspendedTracker> entry = eiter.next(); SuspendedTracker tracker = entry.getValue(); if (tracker.isTimeout()) { // ??? Cellet if (!this.tagContexts.containsKey(tracker.getTag())) { // List<Cellet> list = tracker.getCelletList(); for (Cellet cellet : list) { cellet.quitted(entry.getKey()); } } // eiter.remove(); } } } /** ? */ private SuspendedTracker suspendTalk(TalkSessionContext ctx, int suspendMode) { if (this.suspendedTrackers.containsKey(ctx.getTag())) { SuspendedTracker tracker = this.suspendedTrackers.get(ctx.getTag()); for (Cellet cellet : ctx.getTracker().getCelletList()) { tracker.track(cellet, suspendMode); } return tracker; } SuspendedTracker tracker = new SuspendedTracker(ctx.getTag()); for (Cellet cellet : ctx.getTracker().getCelletList()) { tracker.track(cellet, suspendMode); } tracker.liveDuration = ctx.getTracker().getSuspendDuration(); this.suspendedTrackers.put(ctx.getTag(), tracker); return tracker; } /** ???? */ private synchronized boolean tryResumeTalk(String tag, Cellet cellet, int suspendMode, long startTime) { SuspendedTracker tracker = this.suspendedTrackers.get(tag); if (null != tracker) { boolean ret = tracker.pollPrimitiveMatchMode(this.executor, cellet, suspendMode, startTime); if (ret) { tracker.retreat(cellet); return true; } } return false; } /** ?? */ private boolean tryOfferPrimitive(String tag, Cellet cellet, Primitive primitive) { SuspendedTracker tracker = this.suspendedTrackers.get(tag); if (null != tracker) { tracker.offerPrimitive(cellet, System.currentTimeMillis(), primitive); return true; } return false; } /** ? Session ?? */ private void deliverChecking(Session session, String text, String key) { // ? HTTP ?? Session if (session instanceof HttpSession) { return; } // ? WebSocket Session if (session instanceof WebSocketSession) { byte[] ciphertext = Cryptology.getInstance().simpleEncrypt(text.getBytes(), key.getBytes()); JSONObject data = new JSONObject(); try { JSONObject packet = new JSONObject(); // {"ciphertext": ciphertext, "key": key} packet.put(HttpInterrogationHandler.Ciphertext, Cryptology.getInstance().encodeBase64(ciphertext)); packet.put(HttpInterrogationHandler.Key, key); data.put(WebSocketMessageHandler.TALK_PACKET_TAG, WebSocketMessageHandler.TPT_INTERROGATE); data.put(WebSocketMessageHandler.TALK_PACKET, packet); } catch (JSONException e) { Logger.log(TalkService.class, e, LogLevel.ERROR); } Message message = new Message(data.toString()); this.webSocketManager.write((WebSocketSession) session, message); message = null; return; } // ?| byte[] ciphertext = Cryptology.getInstance().simpleEncrypt(text.getBytes(), key.getBytes()); Packet packet = new Packet(TalkDefinition.TPT_INTERROGATE, 1, 1, 0); packet.appendSubsegment(ciphertext); packet.appendSubsegment(key.getBytes()); byte[] data = Packet.pack(packet); if (null != data) { Message message = new Message(data); this.acceptor.write(session, message); message = null; } packet = null; } private Message packetResume(String targetTag, Long timestamp, Primitive primitive) { // ?||? // ? ByteArrayOutputStream stream = primitive.write(); // ?? Packet packet = new Packet(TalkDefinition.TPT_RESUME, 6, 1, 0); packet.appendSubsegment(Utils.string2Bytes(targetTag)); packet.appendSubsegment(Utils.string2Bytes(timestamp.toString())); packet.appendSubsegment(stream.toByteArray()); // ? byte[] data = Packet.pack(packet); Message message = new Message(data); return message; } /** ? */ private Message packetDialogue(Cellet cellet, Primitive primitive, boolean jsonFormat) { Message message = null; if (jsonFormat) { try { JSONObject primJson = new JSONObject(); PrimitiveSerializer.write(primJson, primitive); JSONObject packet = new JSONObject(); packet.put(HttpDialogueHandler.Primitive, primJson); packet.put(HttpDialogueHandler.Identifier, cellet.getFeature().getIdentifier()); JSONObject data = new JSONObject(); data.put(WebSocketMessageHandler.TALK_PACKET_TAG, WebSocketMessageHandler.TPT_DIALOGUE); data.put(WebSocketMessageHandler.TALK_PACKET, packet); // message message = new Message(data.toString()); } catch (JSONException e) { Logger.log(this.getClass(), e, LogLevel.ERROR); } } else { // ??|Cellet // ? ByteArrayOutputStream stream = primitive.write(); // ?? Packet packet = new Packet(TalkDefinition.TPT_DIALOGUE, 99, 1, 0); packet.appendSubsegment(stream.toByteArray()); packet.appendSubsegment(Utils.string2Bytes(cellet.getFeature().getIdentifier())); // ? byte[] data = Packet.pack(packet); message = new Message(data); } return message; } /** ?? */ protected class Certificate { /** */ protected Certificate() { this.session = null; this.key = null; this.plaintext = null; this.time = System.currentTimeMillis(); this.checked = false; } /// Session protected Session session; /// protected String key; /// protected String plaintext; /// protected long time; /// ???? protected boolean checked; } }