Mês: março 2011

UCC API – Unified Communications Client API – Parte 3 – Publicando Category Instances

Objetivo

Nessa parte vamos discutir o concieto de publicação de category instances na UCC API. A publicação é a idéia de mandar uma informação para o servidor e propagar essa informação para outros usuários, ou seja, mandar seu cartão de contato, criar grupos, adicionar contatos a sua lista de contatos são operações de publicação.

Qunado publicamos uma informação você verá uma propriedade “container id”. Essa propriedade signfica quem pode ver essa publicação. Essa parte é um pouco complicado e pode gerar alguns comportamentos estranhos que veremos no próximo post.

A idéia deste é somente explicar como fazer uma publicação. No próximo veremos como tratar o container id e quem pode ver essa informação.

Publicando

O padrão para fazer uma publicação é relativamente simples:

  • Obter um category instance publicável (publishable category instance)
  • Publicar
  • Ver se a publicação foi bem-sucedida.

Existem duas formas de se obter uma category instance publicável:

  • Através do método CreatePublishableCategoryInstance no IUccPublicationManager
  • Criar uma através de uma category instance recebida por uma publicação (parte 2). O IUccCategoryInstance também possui um método CreatePublishableCategoryInstance.
  • Para mostrar como esse padrão funciona, vamos ver um exemplo de como publicar o estado de um usuário (o estado também é um category instance!).

        public void PublishPresence(MyEndpoint Endpoint, int instanceId, UCC_CATEGORY_INSTANCE_EXPIRE_TYPE expireType, int availability,
          UCC_PRESENCE_STATE_TYPE presenceStateType, bool manual)
        {
          IUccPublicationManager pubManager = (IUccPublicationManager)uccEndpoint;
    
          IUccCategoryInstance pubInstance = (IUccCategoryInstance)pubManager.CreatePublishableCategoryInstance("state", 2, instanceId, expireType, 0);
          
          IUccPresenceStateInstance state = (IUccPresenceStateInstance)pubInstance;
          state.Availability = (int)availability;
          state.Type = presenceStateType;
          state.IsManual = manual;
    
          pubInstance.PublicationOperation = UCC_PUBLICATION_OPERATION_TYPE.UCCPOT_ADD;
          
          IUccPublication pub = pubManager.CreatePublication();
          ComHelper.Advise<_IUccPublicationEvent>(pub, this);
          pub.AddPublishableCategoryInstance(pubInstance);
          pub.Publish();
        }
    

    Primeiro, obtemos a instancia do IUccPublicationManager através do endpoint do usuário conectado. O próximo passo é obter uma category instance publicável (publishable category instance) usando o método CreatePublishableCategoryInstance.

    Este método recebe o “state” que é uma string “reservada” (ou hard-coded, como prefiram). O próximo parâmetro é o ContainerId. Veremos o porque do valor “2” no próximo post deste blog. O outro parâmetro é o InstanceId. O InstanceId depende do tipo de categoria que está sendo publicada. Verifique na documentação da categoria. O expire type determina quando a publicação expira. Está relacionado com o tempo de vida da publicação. O último parâmetro indica o tempo caso o expire type seja tempo (time).

    O mais importante aqui é que publicações do tipo STATIC são armazenadas no servidor. Mesmo se o usuário desconectar, a publicação é mantida no servidor.

    O próximo passo é fazer um cast da categoria para seu tipo especializado. Neste caso, user presence state. Então definios as propriedades específicas ali.

    A operação de publicação geralmente é UCCPOT_ADD. Eu não sei exatamente o porque da existência do UCCPOT_REMOV, já que se você deseja remover uma categoria deve usar o método RemovePublishableCategoryInstance e não um tipo diferente de publicação.

    No próximo passo criamos uma instância de IUccPublication. Isso é feito através do método CreatePublication no publication manager.

    Então, fazemos um advise nos eventos da publicação, adicionamos a category instance publicável e chamamos o método Publish().

    Então, o OnPublish da interface _IUccPublicationEvents será chamado:

        public void OnPublish(IUccPublication pPublication, IUccOperationProgressEvent pPublicationEventInfo)
        {
          if (!pPublicationEventInfo.IsComplete)
            return;
    
          if (pPublicationEventInfo.StatusCode != 0)
            throw new Exception("Error");
        }
    

    Neste método apenas checamos o StatusCode. Se é 0, a publicação foi bem sucedida.

    Publicando Estado de Presença (Presence State)

    Apenas para completar o exemplo, vamos mostrar como publicar estado de presença. O código 3500 está relacionado com o status “Available”. Lá vai o código:

        private void PublishOnlinePresence(object sender, EventArgs e)
        {
          MyPublicationManager myPublicationManager = new MyPublicationManager();
          myPublicationManager.PublishPresence(myEndpoint, 0x20000000, UCC_CATEGORY_INSTANCE_EXPIRE_TYPE.UCCCIET_USER, 3500, UCC_PRESENCE_STATE_TYPE.UCCPST_USER_STATE, true);
    
          MyPublicationManager myPublicationManager2 = new MyPublicationManager();
          myPublicationManager2.PublishPresence(myEndpoint, 0x30000000, UCC_CATEGORY_INSTANCE_EXPIRE_TYPE.UCCCIET_DEVICE, 3500, UCC_PRESENCE_STATE_TYPE.UCCPST_MACHINE_STATE, false);
        }
    

    Neste exemplo, usamos o instance Id 0x20000000 para estado do usuário e 0x30000000 para estado da máquina. Esses valores são específicos para publicação de estado. Verifique a documentação da msdn para outros códigos.

    Conclusão

    Toda vez que houver necessidade de publicar um category instance, apenas siga este padrão. Você precisará saber alguns detalhes específicos sobre a category instance que deseja publicar, como se você está publicando o InstanceId correto e propriedades específicas.

    O ContainerId será explicado no próximo post.

    Espero que ajude.

UCC API – Unified Communications Client API – Parte 2 – Category e Category Instances

Objetivo

Nessa parte veremos o conceito de “Category” e “Category instance” da UCC API. A idéia é ilustrar o padrão existente para fazer as assinaturas e recuperar as informações do servidor.

O que é um “Category” na UCC API

Category é uma abstração de dados. Toda informação consultada do servidor OCS faz parte de uma categoria e é representada por um category instance (instância de categoria).

A forma de assinar essas informações são as mesmas independente de qual categoria é assinada. Para saber informações detalhadas de um category instance, é necessário fazer um cast da instância para a interface COM especializada desta category instance.

Um category instance pode ser:

  • Presença: estado, cartão de contato, notas, etc.:
  • Contatos e grupos
  • Containers: relacionados ao controle de acesso da publicação das category instances
  • Configurações

Você pode encontrar mais informações no MSDN: Category and category instances.

Como obter informações de categorias

A informação de uma categoria pode ser obtida através de uma assinatura. Uma assinatura é criada e é feito um Advise dos eventos nesta categoria. As informações são chamadas através de eventos numa interface de dispatch.

O primeiro passo é fazer um cast do IUccEndpoint para um IUccSubscriptionManager e criar a assinatura:

    public void SubscribeServerConfiguration()
    {
      IUccSubscriptionManager subscriptionManager = (IUccSubscriptionManager)myEndpoint.uccEndpoint;

      IUccSubscription serverConfigurationSubscription = subscriptionManager.CreateSubscription(null);

      UccPresentity presentity = serverConfigurationSubscription.CreatePresentity(myEndpoint.uccEndpoint.Uri, null);
      ComHelper.Advise<_IUccPresentityEvents>(presentity, this);
      serverConfigurationSubscription.AddPresentity(presentity);
      serverConfigurationSubscription.AddCategoryName("ServerConfiguration");
      serverConfigurationSubscription.Query(null);
    }

Esse código cria uma assinatura para uma categoria “ServerConfiguration”. Sim, as categorias são strings hard-coded (ou reservadas, para ser mais elegante).

O myEndpoint.uccEndpoint é um endpoint já inicializado (ver parte 1). O método CreateSubscription instancia um novo objeto “Subscription” (assinatura).

Os objetos “Presentity” são os que vão receber os eventos de assinatura. Eu não sei porque não é o próprio objeto subscription que recebe os eventos, mas deixo essa pergunta para os projetistas da API. Por enquanto, vamos apenas explicar o código acima. Nos fazemos um advise no _IUccPresentityEvents porque ele vai receber a notificação da assinatura no método OnCategoryContextAdded. Isso significa que o objeto presentity ouve esta categoria. Toda vez que uma categoria é adicionada ou removida ocorrerão chamadas nos eventos OnCategoryContextAdded e OnCategoryContextRemoved.

Então chamamos o método Query. Dependendo da categoria assinada (a string hard-coded) você precisa chamar o método Query ou Subscribe. A documentação da MSDN explica qual método chamar para cada uma das categorias.

Seguimos então para a chamada do OnCategoryInstanceAdded:

    public void OnCategoryContextAdded(UccPresentity pPresentity, UccCategoryContextEvent pCategoryCtxtEvent)
    {
      ComHelper.Advise<_IUccCategoryContextEvents>(pCategoryCtxtEvent.CategoryContext, this);
    }

Nós apenas fazemos um advise da interface _IUccCategoryContextEvents. Nesta interface temos os métodos OnCategoryInstanceAdded e OnCategoryInstanceRemoved. Isso significa que toda vez que um valor mudar dentro de uma categoria (ex.: um contato, uma configuração, o estado de um usuário), ocorrerá uma chamada em um destes métodos.

Por último, vamos tratar o evento OnCategoryInstanceAdded:

    public void OnCategoryInstanceAdded(IUccCategoryContext pCategoryCtxt, UccCategoryInstanceEvent pCategoryInstanceEvent)
    {
      if (pCategoryCtxt.Name == "ServerConfiguration")
      {
        IUccServerConfigurationCategory category = (IUccServerConfigurationCategory)pCategoryInstanceEvent.CategoryInstance;
        this.FocusUri = category.FocusFactory;
        MessageBox.Show(FocusUri.Value);
      }
}

Neste código, vamos apenas testar se a categoria é do tipo “ServerConfiguration”. Isso é útil quando você tem mais de uma categoria no mesmo subscription. Você pode usar o mesmo método para tratar uma ou mais assinaturas.

Então fazemos um cast do pCategoryInstanceEvent.CategoryInstance (o category instance recém adicionado no category context) para um IUccServerConfigurationCategory. Nós podemos fazer isso porque nós sabemos (através da documentação da MSDN)) que podemos fazer isso. Cada um dos category contexts possui uma interface equivalente.

Então podemos acessar os valores especializados da category instance, como o FocusUri (útil para conferências).

Informações de Contatos

O MSDN fornece um exmeplo completo que nos mostra como obter informações dos contatos, mas isso não é muito simples de compreender. Como é um pouco difícil entender o exemplo, resolvi escrever essa parte pra tentar manter a coisa simples.

Para assinar a categoria de contatos, fazemos:

    public void SubscribeContacts()
    {
      IUccSubscriptionManager subscriptionManager = (IUccSubscriptionManager)myEndpoint.uccEndpoint;

      IUccSubscription serverConfigurationSubscription = subscriptionManager.CreateSubscription(null);

      UccPresentity presentity = serverConfigurationSubscription.CreatePresentity(myEndpoint.uccEndpoint.Uri, null);
      ComHelper.Advise<_IUccPresentityEvents>(presentity, this);
      serverConfigurationSubscription.AddPresentity(presentity);
      serverConfigurationSubscription.AddCategoryName("contacts");
      serverConfigurationSubscription.Subscribe(null);
    }

É a mesma coisa do exemplo assima, apenas chamando a categoria “contacts”.

Então, na chamada do OnCategoryContextAdded:

    public void OnCategoryContextAdded(UccPresentity pPresentity, UccCategoryContextEvent pCategoryCtxtEvent)
    {
      ComHelper.Advise<_IUccCategoryContextEvents>(pCategoryCtxtEvent.CategoryContext, this);
    }

E na chamada do OnCategoryInstanceAdded:

    public void OnCategoryInstanceAdded(IUccCategoryContext pCategoryCtxt, UccCategoryInstanceEvent pCategoryInstanceEvent)
    {
      if (pCategoryCtxt.Name == "contacts")
      {
        IUccContact contact = (IUccContact)pCategoryInstanceEvent.CategoryInstance;
        MessageBox.Show("Contact: " + contact.Uri.Value + " / " + contact.Name);

        IUccSubscriptionManager subscriptionManager = (IUccSubscriptionManager)myEndpoint.uccEndpoint;
  
        IUccSubscription theContactSubscription = subscriptionManager.CreateSubscription(null);

        UccPresentity presentity = theContactSubscription.CreatePresentity(contact.Uri, null);
        ComHelper.Advise<_IUccPresentityEvents>(presentity, this);
        theContactSubscription.AddPresentity(presentity);
        theContactSubscription.AddCategoryName("contactCard");
        theContactSubscription.AddCategoryName("state");
        theContactSubscription.Subscribe(null);      
      }
  }

O que muda neste exemplo é que criamos um presentity baseado na Uri do contato para então fazermos novas assinaturas para cada um dos contatos e pegar suas informações de contato e presença.

Conclusão

Espero que com esses exemplos extremamente simplistas fique fácil entender a mecânica da assinatura das categorias. Eu escrevi essa parte apenas para ficar mais simples o entendimento de outras mais complexas, como controle de acesso a containers e criação de conferências.

O objetivo desse post é ser somente uma introdução. A documentação da MSDN é muito mais completa como uma referência.

UCC API – Unified Communications Client API – Parte 1 – Visão Geral

Objetivo

Algum tempo atrás, tive a oportunidade de tarabalhar com a Microsoft Unified Communications API, também conhecida como UCC API. Para as pessoas que não estão familiarizadas com esse nome, trata-se da API do Microsoft Office Communicator (OCS).

O OCS disponibiliza esta API para pessoas que querem fazer seu próprio cliente de chat (ou outros tipos de comunicação) utilizando toda a infra-estrutura do servidor.

Enquanto estava mexendo com a API, percebi que a documentação da MSDN não era muito completa e nem muito simples de entender. Essa foi a motivação para postar toda minha experiência aqui.

O forum americano da UCC API não tem muita gente postando e não é muito ativo. A soma de todos estes elementos tornaram o trabalho um pouquinho complicado.

Infelizmente, não tenho muito a colaborar sobre tipos de mídia diferente de IM (voz por exmplo), porém, os conceitos da API são os mesmos quando se trata de outro tipo de mídia.

Visão Geral da UCC API

A API é a mesma para C++ e .Net (C#). Ela é implementada através de um componente COM e um primary interop assembly para .NET. O primeiro problema aqui é que geralmente a API é utilizada com registro “side-by-side” do componente COM.

Aqui está o link da documentação da MSDN que explica isso: Sample Application Manifest for Side-by-Side Execution.

Esse registro através de um arquivo de manifesto é um pouco complicado, mas evita a necessidade de registrar o componente COM (regsvr32) e minimiza problemas de deploy futuros.

O Visual Studio 2008 tem um bug engraçado com manifestos. Mesmo se vc adicionar o .manifest dentro do projeto, sempre que você modifica o csproj (adicionando um arquivo, por exemplo) e pressiona F5, ocorre um erro no momento de carregar o manifesto. Fechando e reabrindo o Visual Studio resolve o problema. Não sei se o mesmo ocorre no VS 2010.

Outra característica da UCC API é que ela é toda assícrona. Na prática, isso significa que se você executa um Endpoint.Enable() por exemplo, um método OnEnable da interface COM é chamada em resposta a essa solicitação. Quando você começa a aninhar esse tipo de chamada, o código fica bem complicado para entender. Se você não conhece a API, fica sem saber onde é o próximo passo no fluxo de execução da aplicação.

Todo objeto COM tem uma interface de “dispatch” amiga. Por exemplo: IUccEndpoint e _IUccEndpointEvents.

O que eu sugiro é que você nunca faça um advise de diferentes interfaces dispatch com cardinalidades diferentes. Por exemplo: Num endpoint você tem “n” sessions. Se você implementar _IUccEndpointEvents e _IUccSessionEvents no mesmo objeto, você está pedindo para ter problemas.

Para objetos de mesma cardinalidade, por exemplo _IUccSessionEvents e _IUccInstantMessagingSessionEvents não tem problema. Isso acontecerá várias vezes porque o COM estimula esse tipo de design. O objeto é o mesmo, porém o polimorfismo no COM é bastante peculiar.

Principais pontos da UCC API

Eu divido a UCC API nos seguintes pontos-chave:

  • Criando conexão com o servidor: É como conectar e desconectar num servidor.
  • Tratando eventos de category instances: Esse tópico trata-se de como receber informações do servidor. Tudo está no mesmo saco: informação de presença do usuário, configuração do servidor, containers, pesquisa.
  • Tratando controle de acesso de informações nos containers: Esse é um ponto complicado da API. Você pode publicar informações diferentes para usuários diferentes, baseado em controle de acesso. Você pode dizer para um usuário que está online e para outro que está ocupado.
  • Tratando conversações: Esse tópico fala sobre mandar e receber mensagens. Iniciar e abandonar sessões de conversação (vale para IM e outros tipos de mídia). Conferência faz parte de sessões de conversação, mas merece um capítulo a parte.

Informações de Login do OCS

As informações necessárias para login são:

  • Uri: A Uri “Sip” do usuário. É necessário informar o prefixo sip:
  • UserName: Nome do usuário. Pode ser o nome completo (user@domain.com) ou apenas user.
  • Password: A senha do usuário
  • Domain: Domínio

Quando vi isso pela primeira vez, pensei comigo: “Porque dois nomes de usuário?”. Continuo me perguntando, mas desenvolvi algumas teorias (não baseadas em nenhuma documentação, apenas num palpite): Está relacionada à estrutura do servidor OCS.

O processo de conexão segue os seguintes passos:

  • A UCC API manda um pacote SIP para o servidor, localizando o usuário no OCS Front End server através de sua URL SIP. O servidor responde: Sim, este usuário é um usuário do OCS.
  • A UCC API manda o usuário/senha/domínio para o OCS Front End Server. O servidor faz um forward dessa informação para o Active Directory que responde: Okay, amigo ou NÃO, você é do mal.

As informações de usuário, senha e domínio podem ser enviadas para o servidor de duas formas:

  • User/Domain/Password: O nome “curto” do usuário. Este é validado no Active Directory’s com o nome “antigo” do usuário (pre-windows 2000). Por isso não precisa de domínio.
  • Full Qualified Name / Password: O formato user@domain.com. Nesse caso, ele vai ser validado contra o “novo” login name no active directory e simplesmente ignora o campo domínio. Mande uma string vazia nele.

Tratando a conexão com o servidor: Endpoint

O conceito neste caso é simples. Você possui um objeto UccPlatform uqe representa uma instância da API na sua aplicação. Você terá apenas um e deverá inicializá-lo uma única vez:

    public void CreatePlatform()
    {
      uccPlatform = new UccPlatform();
      uccPlatform.Initialize("UCCConferenceSample", null);      
    }

Se você quiser derrumar a plataforma, use o método Shutdown.

O endpoint é sua conexão com o servidor. Você precisa de uma plataforma inicializada. Você pode habilitar o endpoint da seguinte forma:

    public void EnableEndpoint(MyPlatform platform, string UserUri, string UserName, string UserPassword, string UserDomain, string ServerName, UCC_TRANSPORT_MODE transportMode)
    {
      UccUri uri = UccHelper.GetUri(UserUri);
      uccEndpoint = platform.uccPlatform.CreateEndpoint(UCC_ENDPOINT_TYPE.UCCET_PRINCIPAL_SERVER_BASED, uri, null, null);

      ComHelper.Advise<_IUccEndpointEvents>(uccEndpoint, this);     


      IUccServerSignalingSettings signalingSettings = (IUccServerSignalingSettings)uccEndpoint;

      UccCredential credential = signalingSettings.CredentialCache.CreateCredential(UserName, UserPassword, UserDomain);
      signalingSettings.CredentialCache.SetCredential("*", credential);
      signalingSettings.AllowedAuthenticationModes = (int)UCC_AUTHENTICATION_MODES.UCCAM_NTLM;
      signalingSettings.Server = signalingSettings.CreateSignalingServer(ServerName, UCC_TRANSPORT_MODE.UCCTM_TCP);

      uccEndpoint.Enable(null);
    }

Vamos discutir um pouco este código. UccHelper é um pequeno wrapper para interpretar Uri’s. Você precisará dele várias vezes. Eu vou fornecer o código logo abaixo. CreateEndpoint é o método principal aqui. Toda vez que aparecer um “Create” na API, não significa que você está “usando” o objeto. Apenas instanciando ele. Esse comportamento também ocorre várias vezes.

ComHelper é outra classe auxiliar. É um wrapper rápido para associar os eventos disparados por uma dispatch interface com um objeto. Neste caso estamos dizendo que os eventos disparados por _IUccEndpointEvents em “this”. Essa é uma característica do COM.

A classe precisa implementar os métodos da interface _IUccEndpointEvents e terá os métodos OnEnable ou OnDisable disparados assincronamente após a chamada do método Enable.

  public class ComHelper
  {
    public static void Advise<T>(object source, T sink)
    {
      IConnectionPointContainer container = (IConnectionPointContainer)source;
      IConnectionPoint cp;
      int cookie = 0;
      Guid guid = typeof(T).GUID;

      container.FindConnectionPoint(ref guid, out cp);
      cp.Advise(sink, out cookie);
    }
  }

O cast que aparece aqui é muito interessante também. Quando você faz um cast (IUccServerSignallingSettings)uccEndpoint, internamente você está chamando o método QueryInterface do COM. Isso confunde muito desenvolvedores .Net porque quando você está inspecionando o código, não é possível perceber que a instância uccEndpoint “é” um IUccServerSignallingSettings. Você só tem essa informação lendo a documentação da API (os links estão no rodapé do post). Quando a documentação diz IUccAlgumaCoisa can be queried from the IUccOutraCoisa significa que no C# você pode fazer um cast direto.

As próximas linhas referentes ao “signalling settings” estão apenas passando as informações de conexão antes de chamar o método Enable.

A resposta será enviada pelo servidor no método OnEnable:

    public void OnEnable(IUccEndpoint pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Bad luck");
      MessageBox.Show("Endpoint enabled");      
    }

Os controles para evitar que sua aplicação faça algo antes de receber a chamada em OnEnable devem ser feitos por sua conta.

A interface IUccOperationProgressEvent tem uma propriedade chamada StatusCode. Se for diferente de 0, ocorreu um problema. Sempre verifique isso em cada resposta.

Para compreender melhor os erros, ligue o “Hexadecimal View” do inspetor do C# e ele fará um pouco mais de sentido (não muito). Você pode encontrar o significado para os códigos de erro nos links:

Unified Communications Client API Error Codes
Unified Communications Client API Error Messages

Conclusão

Essa primeira parte não ajudará muito, mas é um começo. Em breve estarei trazendo os outros posts com os próximos passos.

Espero que com essas informações seja possível compreender um pouco das características da UCC API que ajudem no desenho de um novo cliente baseado nesta API e como funciona o processo de conexão com um servidor OCS.

Links úteis

MSDN SDK Documentation.
MSDN Forum