Package net.solosky.maplefetion.client.dialog

Source Code of net.solosky.maplefetion.client.dialog.LiveV2ChatDialog

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/

/**
* Project  : MapleFetion2
* Package  : net.solosky.maplefetion.client.dialog
* File     : LiveV2ChatDialog.java
* Author   : solosky < solosky772@qq.com >
* Created  : 2010-1-14
* License  : Apache License 2.0
*/
package net.solosky.maplefetion.client.dialog;

import java.util.ArrayList;
import java.util.Iterator;

import net.solosky.maplefetion.ExceptionHandler;
import net.solosky.maplefetion.FetionConfig;
import net.solosky.maplefetion.FetionContext;
import net.solosky.maplefetion.FetionException;
import net.solosky.maplefetion.bean.Buddy;
import net.solosky.maplefetion.bean.Message;
import net.solosky.maplefetion.chain.ProcessorChain;
import net.solosky.maplefetion.client.SystemException;
import net.solosky.maplefetion.client.dispatcher.LiveV2MessageDispatcher;
import net.solosky.maplefetion.client.response.DefaultResponseHandler;
import net.solosky.maplefetion.client.response.SendChatMessageResponseHandler;
import net.solosky.maplefetion.event.ActionEventType;
import net.solosky.maplefetion.event.action.ActionEventFuture;
import net.solosky.maplefetion.event.action.ActionEventListener;
import net.solosky.maplefetion.event.action.FutureActionEventListener;
import net.solosky.maplefetion.net.Port;
import net.solosky.maplefetion.net.RequestTimeoutException;
import net.solosky.maplefetion.net.Transfer;
import net.solosky.maplefetion.net.TransferException;
import net.solosky.maplefetion.net.TransferFactory;
import net.solosky.maplefetion.net.TransferService;
import net.solosky.maplefetion.sipc.SipcHeader;
import net.solosky.maplefetion.sipc.SipcNotify;
import net.solosky.maplefetion.sipc.SipcOutMessage;
import net.solosky.maplefetion.sipc.SipcRequest;
import net.solosky.maplefetion.sipc.SipcResponse;
import net.solosky.maplefetion.sipc.SipcStatus;
import net.solosky.maplefetion.util.BuddyEnterHelper;
import net.solosky.maplefetion.util.CrushBuilder;
import net.solosky.maplefetion.util.ResponseFuture;
import net.solosky.maplefetion.util.SipcLogger;
import net.solosky.maplefetion.util.SipcParser;
import net.solosky.maplefetion.util.TicketHelper;

import org.apache.log4j.Logger;

/**
*
* 第二版和在线好友聊天对话框
* <pre>
* 这个就是飞信的Im-relay模式的实现,仅在支持多个独立的连接下和在线用户建立会话有效。
* 这个对话的建立不是通过飞信主服务器,而是是通过另外一个飞信的聊天服务器
*
* 假如A邀请B建立会话,简单的流程是这样的:
*    1. A给主服务器说,我要建立会话,请给我聊天服务器的IP和进入凭证,服务器返回了聊天服务器的IP和凭证;
*    2. A去连接返回的聊天服务器,并交给聊天服务器进入凭证,说,我想开始一个会话,服务器返回OK;
*    3. A再给聊天服务器说,我要邀请B,这是B的地址,服务器回复说,好的,请稍等下,我去邀请B;
*    4. 过了一会儿,聊天服务器回复说,我已经成功的邀请了B,请等待B进入对话;
*    5. 又过了一会儿,聊天服务器通知说,B已经进入了对话,你们可以开始交谈了;
* ...之后A就可以向聊天服务器发送消息,B就会收到。B发送的消息A也会收到。
*    6. A说,对不起我有点事,我要离开对话,服务器说OK
* 到此对话结束。
*
* 假设A被B邀请进入会话,流程是这样的。
*   1. 主服务器告诉A,B邀请你对话,这是聊天服务器的IP和凭证,A告诉主服务器,OK,我同意进入会话。
*   2. A去连接聊天服务器,并给服务器进入凭证,说我要加入一个会话,服务器返回OK;
*   3. 聊天服务器马上告诉A,B已经进入了对话,你们可以开始交谈了;
*    ...之后A就可以向聊天服务器发送消息,B就会收到。B发送的消息A也会收到。
*   4. B说,我下线了,我要离开对话,服务器说OK
*  
* 这里简单的说明了一个用户邀请另外一个用的过程,其实这个对话是支持多方对话的,
* 任何进入对话的好友都可以邀请在线的好友加入会话,过程都是差不多的。
* 只要向这个对话发送消息,所有进入这个对话的好友都会收到你发送的消息;
* </pre>
* @author solosky <solosky772@qq.com>
*/
public class LiveV2ChatDialog extends ChatDialog implements MutipartyDialog, ExceptionHandler
{

  /**
   * 处理链
   */
  private ProcessorChain processorChain;
 
  /**
   * 消息工厂
   */
  private MessageFactory messageFactory;
 
  /**
   * 好友进入等待对象
   */
  private BuddyEnterHelper buddyEnterHelper;
 
  /**
   * 邀请通知
   */
  private SipcNotify inviteNotify;
 
  /**
   * 所有进入这个对话框的好友列表
   */
  private ArrayList<Buddy> buddyList;
 
  /**
   * LOGGER
   */
  private static Logger logger = Logger.getLogger(LiveV2ChatDialog.class);
 
  /**
   * @param mainBuddy
   * @param client
   */
  public LiveV2ChatDialog(Buddy mainBuddy, FetionContext client)
  { 
    super(mainBuddy, client);
    this.messageFactory   = new MessageFactory(client.getFetionUser());
    this.buddyEnterHelper = new BuddyEnterHelper();
    this.buddyList        = new ArrayList<Buddy>();
  }

  /**
   * 以一个邀请通知构建
   * @param inviteNotify
   * @param client
   */
  public LiveV2ChatDialog(SipcNotify inviteNotify, FetionContext client)
  {
    super(client);
    this.inviteNotify   = inviteNotify;
    this.mainBuddy      = client.getFetionStore().getBuddyByUri(inviteNotify.getFrom());
    this.messageFactory = new MessageFactory(client.getFetionUser());
    this.buddyEnterHelper = new BuddyEnterHelper();
    this.buddyList        = new ArrayList<Buddy>();
  }
 
  /**
   * 建立处理链
   *
   * @param host
   * @param port
   * @throws FetionException
   * @throws TransferException
   */
  private void buildProcessorChain(Transfer transfer) throws FetionException
  {

    this.processorChain = new ProcessorChain();
    this.processorChain.addLast(new LiveV2MessageDispatcher(context, this, this));         // 消息分发服务
    if(FetionConfig.getBoolean("log.sipc.enable"))
      this.processorChain.addLast(new SipcLogger("LiveV2ChatDialog-" + mainBuddy.getFetionId()));                 // 日志记录
    this.processorChain.addLast(new TransferService(this.context));                           // 传输服务
    this.processorChain.addLast(new SipcParser());                          //信令解析器
    this.processorChain.addLast(transfer);                              //传输对象
   
    this.processorChain.startProcessorChain();
  }
 
  /**
   * 尝试建立连接,在可用的端口建立连接,直到建立一个可用的连接
   * @param portList
   * @return
   */
  private Transfer buildTransfer(ArrayList<Port> portList)
  {
    TransferFactory factory = this.context.getTransferFactory();
    Transfer transfer = null;
    Port     port     = null;
    Iterator<Port> it = portList.iterator();
    while(it.hasNext()) {
      port = it.next();
      //尝试建立连接
      try {
        logger.trace("try to connect to port = "+port+" ..");
              transfer = factory.createTransfer(port);
            } catch (TransferException e) {
              logger.trace("Connect to port failed - Port = "+port, e);
            }
           
            //如果建立成功就跳出循环,否则继续尝试建立下一个端口的连接
      if(transfer!=null) {
        logger.trace("Transfer created success. - Transfer="+transfer.getTransferName());
        break;
      }
    }
    return transfer;
  }
   /**
     * 发送数据包
     */
    @Override
    public void process(SipcOutMessage out)
    {
      try {
          this.processorChain.getFirst().processOutcoming(out);
        } catch (FetionException e) {
          this.handleException(e);
          if(out instanceof SipcRequest) {
            SipcRequest request = (SipcRequest) out;
            if(request.getResponseHandler()!=null)
              request.getResponseHandler().ioerror(request);
          }
        }
    }
  /**
   * 返回处理链
     * @return
     */
    public ProcessorChain getProcessorChain()
    {
      return this.processorChain;
    }
   
    /**
     * 用户是否被邀请进入这个对话框的
     * @return
     */
    public boolean isBeenInvited()
    {
      return this.inviteNotify!=null;
    }

  /* (non-Javadoc)
     * @see net.solosky.maplefetion.client.dialog.ChatDialog#isMutipartySupported()
     */
    @Override
    public boolean isMutipartySupported()
    {
      return false;
    }
   

  /* (non-Javadoc)
     * @see net.solosky.maplefetion.client.dialog.ChatDialog#sendChatMessage(java.lang.String, net.solosky.maplefetion.client.dialog.ActionEventListener)
     */
    @Override
    public void sendChatMessage(Message message, ActionEventListener listener)
    {
       this.ensureOpened();
       SipcRequest request = this.messageFactory.createSendChatMessageRequest(this.mainBuddy.getUri(), message);
          request.setResponseHandler(new SendChatMessageResponseHandler(context, this, listener));
          this.process(request);
          this.updateActiveTime();
    }

  /* (non-Javadoc)
     * @see net.solosky.maplefetion.client.dialog.Dialog#doCloseDialog()
     */
    @Override
    protected void doCloseDialog() throws Exception
    {
    //TODO NOTE:如果发生了传输异常这里就不应该发送离开消息,否则会抛出第二个TransferException
      if(this.processorChain!=null && !this.processorChain.isChainClosed()) {
        this.bye();
        this.processorChain.stopProcessorChain();
      }
    }

  /**
   * 打开第二版聊天对话框,这个比较麻烦
   */
    @Override
    protected void doOpenDialog() throws Exception
    {
        //首先要获取进入聊天服务器的凭证
        String ticket = null;
        if(this.isBeenInvited()) {
          //如果被邀请,进入凭证是在邀请通知里
          ticket = this.inviteNotify.getHeader(SipcHeader.AUTHORIZATION).getValue();
        }else {
          //如果不是,用户主动发起会话请求,需要向服务器获取进入凭证
          ticket = this.context.getDialogFactory().getServerDialog().startChat();
        }
        TicketHelper helper = new TicketHelper(ticket);
       
        //然后连接聊天服务器,建立处理链
        Transfer transfer = this.buildTransfer(helper.getPortList());
        if(transfer==null) throw new TransferException("Cannot connect to chat server.");
        this.buildProcessorChain(transfer);
       
        //发送注册信息
        this.register(helper.getCredential());
       
        //如果是主动邀请,邀请好友进入对话框
        if(!this.isBeenInvited())
          this.invite();
       
        //等待好友进入对话框
        int waitTimeout = FetionConfig.getInteger("fetion.dialog.wait-buddy-enter-timeout");
        this.buddyEnterHelper.waitBuddyEnter(this.mainBuddy, waitTimeout*1000);
    }
   
   
    /**
     * 注册服务器
     * @throws InterruptedException
     * @throws RequestTimeoutException
     * @throws IllegalResponseException
     * @throws TransferException
     */
    private void register(String credential) throws RequestTimeoutException, InterruptedException, IllegalResponseException, TransferException
    {
      //发送注册信息
    SipcRequest request = this.messageFactory.createRegisterChatRequest(credential);
    ResponseFuture future = ResponseFuture.wrap(request);
    this.process(request);
    SipcResponse response = future.waitResponse();
    assertStatus(response.getStatusCode(), SipcStatus.ACTION_OK);
    }
   
   
    /**
     * 邀请好友进入对话框
     * @param buddy
     * @throws IllegalResponseException
     * @throws RequestTimeoutException
     * @throws InterruptedException
     * @throws TransferException
     * @throws SystemException
     */
    private void invite() throws IllegalResponseException, RequestTimeoutException, InterruptedException, TransferException, SystemException
    {
      ActionEventFuture future = new ActionEventFuture();
      this.inviteBuddy(this.mainBuddy, new FutureActionEventListener(future));
      assertActionEvent(future.waitActionEventWithException(), ActionEventType.SUCCESS);
    }
   
    /**
     * 离开对话
     * @throws TransferException
     */
    private void bye() throws TransferException
    {
      SipcRequest request = this.getMessageFactory().createLogoutRequest(this.mainBuddy.getUri());
      this.process(request);
    }

  /* (non-Javadoc)
     * @see net.solosky.maplefetion.client.dialog.MutipartyDialog#getBuddyByUriList()
     */
    @Override
    public ArrayList<Buddy> getBuddyList()
    {
      return this.buddyList;
    }

  /* (non-Javadoc)
     * @see net.solosky.maplefetion.client.dialog.MutipartyDialog#inviteBuddy(net.solosky.maplefetion.bean.FetionBuddy, net.solosky.maplefetion.client.dialog.ActionEventListener)
     */
    @Override
    public void inviteBuddy(Buddy buddy, ActionEventListener listener)
    {
     SipcRequest request = this.messageFactory.createInvateBuddyRequest(this.mainBuddy.getUri());
     request.setResponseHandler(new DefaultResponseHandler(listener));
        this.process(request);
    }

  /**
   * 异常回调函数
   * 注意:这里的异常不是在调用者调用方法的时候发生的,而是在传输对象读取一个接受消息时产生的异常,是由客户端内部产生的
   * 比如读取数据时产生传输异常,或者处理异步通知的时发生的异常均由这个方法处理,如果调用者调用某个方法,一般只发生传输异常
   */
    @Override
    public void handleException(FetionException e)
    {
      //如果是网络错误,关闭这个对话,其他错误可以忽略掉
      if(e instanceof TransferException) {
        try {
          this.processorChain.stopProcessorChain(e);
              this.closeDialog();
              logger.warn("LiveV2ChatDialog connection error, closed this dialog.");
            } catch (FetionException e1) {
              logger.warn("close LiveV2ChatDialog failed.", e1);
            }
      }else{
        //记录这个错误
          logger.warn("LiveV2ChatDialog got a exception, just ignore it..", e);
      }
     
      //如果是系统异常,报告这个错误
      if(e instanceof SystemException) {
        CrushBuilder.handleCrushReport(e, ((SystemException) e).getArgs());
      }
    }

  /* (non-Javadoc)
     * @see net.solosky.maplefetion.client.dialog.Dialog#getMessageFactory()
     */
    @Override
    public MessageFactory getMessageFactory()
    {
      return this.messageFactory;
    }

  /* (non-Javadoc)
     * @see net.solosky.maplefetion.client.dialog.MutipartyDialog#buddyEntered(net.solosky.maplefetion.bean.Buddy)
     */
    @Override
    public void buddyEntered(Buddy buddy)
    {
    this.buddyEnterHelper.buddyEntered(buddy);
    this.buddyList.add(buddy);
    }

  /* (non-Javadoc)
     * @see net.solosky.maplefetion.client.dialog.MutipartyDialog#buddyLeft(net.solosky.maplefetion.bean.Buddy)
     */
    @Override
    public void buddyLeft(Buddy buddy)
    {
      //如果是当前对话框的所有者离开,关闭这个对话框
      if(buddy.getUri().equals(this.mainBuddy.getUri())) {
              this.closeDialog();
      }else {    //移除对应离开的好友
        Iterator<Buddy> it = this.buddyList.iterator();
        while(it.hasNext()) {
          Buddy b = it.next();
          if(b.getUri().equals(buddy.getUri())) {
            it.remove();
            break;
          }
        }
      }
    }
   
  @Override
  public void buddyFailed(Buddy buddy) {
    //如果是当前对话框的所有者离开,关闭这个对话框
      if(buddy.getUri().equals(this.mainBuddy.getUri())) {
              this.closeDialog();
      }
  }
 
  /**
   * 发送一个震屏信息给对方
   * @param listener
   */
  public void sendNudgeState(ActionEventListener listener){
     this.ensureOpened();
     SipcRequest request = this.messageFactory.createSendChatStateRequest("nudge");
     request.setResponseHandler(new DefaultResponseHandler(listener));
        this.process(request);
  }
 
  /**
   * 发送用户正在输入状态
   * @param listener
   */
  public void sendInputState(ActionEventListener listener){
    this.ensureOpened();
    SipcRequest request = this.messageFactory.createSendChatStateRequest("input");
     request.setResponseHandler(new DefaultResponseHandler(listener));
        this.process(request);
  }
   
    public String toString()
    {
      return "[LiveV2ChatDialog - " +
          "MainBuddy="+mainBuddy.getDisplayName()+", "+mainBuddy.getUri()+" ]";
    }
}
TOP

Related Classes of net.solosky.maplefetion.client.dialog.LiveV2ChatDialog

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.