Mês: abril 2011

Validando XML Schema em .NET

O que XML Schema? Onde é usado

Recentemente precisei trabalhar com algum formato de arquivos em XML e resolvi fazer um XML Schema.

Mas pra que serve esse tal de XML Schema?

Basicamente o XML Schema é um formato para detalhar ainda mais um arquivo XML. O XML descreve uma estrutura de namespaces, nós, valores, atributos que podem ser usados arbitrariamente por uma aplicação, porém, se quisermos restringir mais ainda o formato, podemos usar um XML Schema.

O XML Schema determina: tipos de valores dos nós, seqüência de nós, ordem, qual nó é valido dentro de outro nó e assim sucessivamente.

É um padrão relativamente complexo, mas que paga o preço, já que muitas ferramentas sabem lidar com esse formato e praticamente toda framework de desenvolvimento hoje sabe validar um XML contra um Schema.

De uns tempos pra cá tem sido usado em muitos lugares, entre eles:

  • Visual Studio: Para gerar classes a partir de XSD
  • Protocolo SOAP: É utilizado internamente pelo protocolo SOAP para detalhar o formato dos tipos e valores das chamadas
  • Ferramentas de integração como SAP PI (Process Integration) e outras

Por essas e outra razões, vale a pena usar.

Exemplos

Os exemplos abaixo foram extraídos do W3Schools:

Exemplo de um arquivo XML:

<?xml version="1.0" encoding="ISO-8859-1"?>

<shiporder orderid="889923"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="shiporder.xsd">
  <orderperson>John Smith</orderperson>
  <shipto>
    <name>Ola Nordmann</name>
    <address>Langgt 23</address>
    <city>4000 Stavanger</city>
    <country>Norway</country>
  </shipto>
  <item>
    <title>Empire Burlesque</title>
    <note>Special Edition</note>
    <quantity>1</quantity>
    <price>10.90</price>
  </item>
  <item>
    <title>Hide your heart</title>
    <quantity>1</quantity>
    <price>9.90</price>
  </item>
</shiporder>

Exemplo de um XSD que valida o XML acima:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="shiporder">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="orderperson" type="xs:string"/>
      <xs:element name="shipto">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="address" type="xs:string"/>
            <xs:element name="city" type="xs:string"/>
            <xs:element name="country" type="xs:string"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="item" maxOccurs="unbounded">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="title" type="xs:string"/>
            <xs:element name="note" type="xs:string" minOccurs="0"/>
            <xs:element name="quantity" type="xs:positiveInteger"/>
            <xs:element name="price" type="xs:decimal"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
    <xs:attribute name="orderid" type="xs:string" use="required"/>
  </xs:complexType>
</xs:element>

</xs:schema>

Como validar um XSD em .NET

Na framework do .NET, temos prontinho como validar um XSD. Houveram algumas mudanças recentes, algumas API’s antigas foram anotadas como “deprecated”. Por isso, me motivei a jogar isso num post.

  public class SchemaValidator
  {
    public bool ValidationErrors = false;

    public void ValidateXML(string XMLFile, string XSDFile)
    {
      this.ValidationErrors = false;

      FileStream xsdStream = new FileStream(XSDFile, FileMode.Open);
      FileStream xmlStream = new FileStream(XMLFile, FileMode.Open);

      XmlReader schemaReader = XmlReader.Create(xsdStream);

      XmlReaderSettings readerSettings = new XmlReaderSettings();
      readerSettings.ValidationType = ValidationType.Schema;
      readerSettings.Schemas.Add("", schemaReader);
      readerSettings.ValidationEventHandler += OnValidate;

      this.ValidationErrors = false;
      XmlReader xr = XmlReader.Create(xmlStream, readerSettings);
      using (xr)
        while (xr.Read()) ;

      xsdStream.Close();
      xmlStream.Close();
    }

    private void OnValidate(object sender, ValidationEventArgs e)
    {
      this.ValidationErrors = true;
      Console.WriteLine(e.Message);
    }

  }

Como o XmlReader lê o XML em pedaços, sem a necessidade de carregá-lo todo para memória, a validação do schema também ocorre em pedaços. Por essa razão existe a necessidade de assinar o evento OnValidate e verificar se houve erro na validação.

MSBuild

Para quem gosta de MSBuild, a task Xml do MSBuild Extensions Pack possui uma task prontinha para usá-la.

Tutorial completo

Lá no W3Schools tem um tutorial completinho de XML Schema: XML Schema Tutorial.

Código fonte

Segue o código fonte de exemplo: SchemaValidation.

A Arquitetura Perfeita

O arquiteto de software

Recentemente venho acompanhando o aumento da procura pelo “Arquiteto de software”, principalmente em plataforma .Net. Há 5 anos atrás parecia que o mercado tinha uma séria dificuldade em entender o que faz um arquiteto. De repente, parece que ele está virando a bola da vez.

Um dos sintomas desta procura são artigos como este: Best Jobs in America 2010.

Parece que de uns tempos pra cá após inúmeros projetos que precisam ser completamente reescritos porque as decisões de projeto e/ou falta de processos os levaram ao caos, o mercado vem procurando profissionais que conseguem corrigir, minimizar ou mesmo prever e evitar tais catástrofes. Talvez esta seja a principal razão pela busca de profissionais.

Mas afinal, o que faz um arquiteto de software?

Essa pergunta tem várias respostas e desconheço uma definição muito clara sobre o papel. Tem aquela clássica analogia com construção civil, ou seja, da mesma forma que na construção civil o arquiteto é quem planeja e projeta a construção, o arquiteto de software é quem faz as plantas e toma as decisões sobre como o software deve ser construido.

Na prática, eu gosto muito da definição dada no livro Microsoft .NET – Architecting Applications for the Enterprise: Arquiteto é o responsável por tomar as decisões de tudo aquilo que é muito difícil de mudar no software.

Faz bastante sentido.

Software como negócio

Querendo ou não, cada vez mais vemos o software não sendo vendido como produto final. No início o modelo de negócio era muito simples. O software era desenvolvido, a licença de uso era vendida. Negócio perfeito. Ganho por venda e meu estoque é infinito.

Os tempos foram mudando. Hoje em dia é praticamente impossível vender uma licença de uso de um software. Tem praticamente tudo de graça. Sistema operacional, web server, instant messaging, suítes do tipo “office”. Acho que a única empresa que ainda ganha com este modelo é a Microsoft. E ela vem tentando novos modelos.

De resto, vemos empresas que utilizam-se do software para vender publicidade (Google e grande parte da Web), dependem de tecnologia para reduzir custos operacionais (bancos, manufatura), venda de mão de obra (ERP’s) e consultoria. Existem muito mais integradores do que criadores de software.

Cada vez mais os modelos apontam para cobrar pelo uso do software, por transação trafegada pelo sistema, pelo serviço fornecido pelo software. Cada vez menos licenças de uso, cada vez mais cloud computing ou software as a service – SaaS.

Seguindo essa linha de raciocínio, cada vez mais o software é uma atividade que gera outros negócios ou apóia outros negócios. Muito pouco se vê sobre vender software como atividade final.

A arquitetura perfeita

As vezes conversando nos forums, com colegas de profissão eu percebo que como profissionais técnicos, vejo muitos arquitetos estão sempre buscando a solução de software perfeita. Aquele único modelo de arquitetura que atende todos os casos, é perfeito e impecável. A solução tamanho único. A excelência técnica.

Essa corrente de pensamento é bastante contrária ao cenário da evolução dos modelos de negócio apoiados em software, que é bem mais complexa e envolvem diversos outros fatores. Para alegria de alguns e desespero de outros, cada vez mais a tecnologia existe para suportar outros modelos de negócio. E dentro dessa ótica, não é somente a excelência técnica que vai fazer do software um produto de sucesso (apesar dela ser extremamente importante).

É só lembrarmos de tantos exemplos que tivemos na história, o maior de todos talvez seja o próprio Windows, que mesmo com suas inúmeras telas azuis em suas versões 95 e 98 tornou-se o sistema operacional mais popular, mesmo existindo diversas alternativas comerciais e gratuitas. Quando essas alternativas tentaram estabelecer-se no mercado, o Windows já o tinha dominado completamente.

O diferencial

Se somente o conhecimento técnico não é suficiente, quais são os outros atributos?

Muitas das decisões técnicas dos projetos não são somente decisões técnicas. Envolvem fornecedores, custos, aquisições, propriedade intelectual e tantas outras complicações que somente uma área tão complexa, dinâmica e com tantos conhecimentos específicos e distintos pode nos proporcionar.

Muitas dessas decisões são extremamente complexas para um executivo que não tenha tido vivência em posições técnicas no passado, ou mesmo não conseguiu acompanhá-la no nível de detalhe necessário. A comunicação necessária para ponderar riscos, custos e qualidade depende de um profissional que saiba trafegar muito bem na fronteira entre pensamento estratégico e conhecimento técnico. O arquiteto de software tem sido um grande colaborador nesta lacuna.

Além de suas habilidades técnicas, pode contribuir com muitos pontos durante uma reflexão estratégica para conceber um novo produto ou negócio:

  • Time to market: O arquiteto pode ajudar a estabelcer a melhor qualidade possível do produto final garantindo que o mesmo esteja disponível a tempo de ser competitivo no mercado.
  • Dependência de fornecedores: O arquiteto pode ajudar a avaliar se os componentes e tecnologias de terceiro estão no nível de maturidade necessário, ou se existe tendência de continuidade, bem como avaliar se os serviços contratados de terceiros possuem os padrões de qualidade necessários.
  • Capacidade de evolução: O arquiteto pode ajudar a organizar o desenvolvimento de forma a prepará-lo para evolução sustentável.
  • Experiência da equipe: O arquiteto pode ajudar a aproveitar as melhores qualidades ou contornar deficiências das equipes de desenvolvimento e produção, estabelecendo padrões e tecnologias condizentes com a experiência do time. Deve levar em consideração também o momento do mercado de profissionais, visto que atualmente fala-se muito no “apagão de profissionais”.

Essa lista é extremamente extensa e não-exaustiva.

Tendo todos estes fatores em mente, chego a conclusão que é muito difícil estabelcer uma arquitetura perfeita.

A começar pela idéia de que se perguntarmos para 10 pessoas diferentes qual é a arquitetura perfeita, provavelmente teremos 10 opiniões diferentes, levando em consideração somente o fator excelência técnica.

Aqui está o principal diferencial do bom arquiteto: saber ouvir os principais stakeholders do projeto, compreender suas necessidades, ser colaborador atuante no processo de reflexão estratégica e conseguir levar estas decisões para o time técnico na linguagem apropriada e no nível de detalhe apropriado.

O primeiro passo para estabelecer uma arquitetura bem-sucedida é conseguir visualizar todos estes atributos e traçar os objetivos desejados, levando em consideração o momento da empresa, do mercado, da disponibilidade de mão de obra, dos fornecedores e da tecnologia em questão. Somente tendo este ponto de partida, é possível escolher os padrões e tecnologias que resultem numa solução equilibrada e num produto de sucesso.

UCC API – Unified Communications Client API – Parte 6 – Conferências

Overview

No post anterior, vimos como criar uma sessão do tipo instant messaging. As sessões do tipo conferência usam praticamente a mesma mecânica.

A diferença é que a conference session é o lugar onde será criada uma conferência no OCS server.

Cada participante deve se adicionar na sessão.

Tudo parece muito fácil na teoria, mas alguams particuularidades na API tornam o trabalho um pouco complicado.

Assim como nas sessões de instant messaging, precisamos tratar os dois caminhos: outgoing session e incoming session.

Fluxo de criação da conferência

Este desenho mostra a idéia geral de como é o processo de criação de conferência na UCC API:

  • O criador da conferência cria um “lugar” no servidor onde a conferência vai acontecer.
  • O criador da conferência manda o endereço deste lugar para os usuários convidados.
  • Cada usuário convidado cria uma sessão de comunicação entre ele e o lugar onde a conferência acontece.

Obtendo a Focus Uri

O primeiro desafio é obter o focus Uri. O Focus URI é um “lugar” no servidor onde os usuários criam as sessões de conferência. Nós precisamos obter a Focus Uri para poder obter uma Conference Uri posteriormente.

Para obter a Focus Uri, precisamos fazer uma “query” na categoria “Server Configuration”.

    public void SubscribeServerConfiguration(IUccEndpoint endpoint)
    {
      IUccSubscriptionManager subscriptionManager = (IUccSubscriptionManager)endpoint;

      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);
    }

A idéia por trás deste código está descrita em: Category e Category Instance.

As informações são devolvidasp elo servidor no método _IUccCategoryContextEvents.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);
      }
    }

O valor que precisamos é devolvido em FocusUri.Value.

Criando uma conferência (ou Scheduling Conference)

O processo de criação de uma conferência no servidor é chamado de “Schedule”. As vezes nos confunde porque pensamos em criar uma conferência no futuro, mas a idéia aqui é criar um lugar no servidor onde as pessoas vão compartilhar a conversação (idéia similar a salas de chat).

Para fazer o schedule da conferência:


    public void ScheduleConference(IUccEndpoint endpoint, string strFocusUri)
    {
      UccUriManager uriManager = new UccUriManager();
      UccUri focusUri = uriManager.ParseUri(strFocusUri);

      IUccConferenceManager confManager = (IUccConferenceManager)endpoint;
      IUccConferenceManagerSession confManagerSession = confManager.CreateConferenceManagerSession(focusUri);

      ComHelper.Advise<_IUccConferenceManagerSessionEvents>(confManagerSession, this);

      UccConferenceInformation ci = confManagerSession.CreateConferenceInformation();
      ci.Properties.AddProperty((int)UCC_CONFERENCE_INFORMATION_PROPERTY.UCCCIP_ADMISSION_TYPE,   UCC_CONFERENCE_ADMISSION_TYPE.UCCCAT_OPEN_AUTHENTICATED);

      UccPropertyCollection mcuAttributeCollection = new UccPropertyCollection();
      mcuAttributeCollection.AddProperty((int)UCC_CONFERENCE_ENTITY_SETTING_PROPERTY.UCCCESP_TYPE, UCC_CONFERENCE_ENTITY_TYPE.UCCCET_INSTANT_MESSAGING);

      UccPropertyCollection mcuPropertyCollection = new UccPropertyCollection();
      mcuPropertyCollection.AddProperty((int)UCC_CONFERENCE_ENTITY_TYPE.UCCCET_INSTANT_MESSAGING, mcuAttributeCollection);

      ci.Properties.AddProperty((int)UCC_CONFERENCE_INFORMATION_PROPERTY.UCCCIP_MCUS, mcuPropertyCollection);

      UccOperationContext operationContext = new UccOperationContext();
      UccContext mcuContext = new UccContext();
      mcuContext.AddNamedProperty("ScheduleConference", true);
      operationContext.Initialize(1, mcuContext);

      confManagerSession.ScheduleConference(ci, operationContext);
    }

Este código tem muita coisa específica da UCC API, mas vou tentar explicar a idéia geral. Primeiro fazemos um cast do endpoint para IUccConferenceManager.

Então precisamos criar uma sessão do conference manager. Precisamos assinar sua interface dispatch para ouvir o evento OnScheduleConference. O Conference Uri é obtido através deste evento.

Após este passo, criamos um objeto Conference Information. Aqui apenas colcoamos algumas informações de quem pode participar dessa conferência, que tipos de mídia são suportados.

Então criamos a propriedade ScheduleConference (acho que isso é fixo no servidor OCS) e chamamos o método ScheduleConference.

A resposta é obtida no método OnScheduleConference:


    public void OnScheduleConference(UccConferenceManagerSession pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Error scheduling conference");

      UccProperty confUriProperty = pEventData.Properties.get_Property((int)UCC_CONFERENCE_MANAGER_OPERATION_COMPLETED_EVENT_PROPERTY.UCCCMOCEP_CONFERENCE_URI);
      conferenceUri = confUriProperty.StringValue;
    }

Aqui, pegamos a Conference Uri como uma propriedade.

Se algo der errado, recebemos StatusCode diferente de 0.

Precisamos guardar esse conference uri, pois precisaremos dele no próximo passo.

Criando uma Conference Session

Uma conference session é a idéia de um usuário interagindo com uma “scheduled conference” (aquilo que fizemos no passo anterior). Não é só o criador da conferência quem cria conference sessions. Veremos adiante que quando convidamos um usuário para uma conferência (uma incoming session), vamos usar o mesmo processo para interagir com a conferência.

Para criar a conference session, precisamos da conference Uri.

O processo segue:


    public void CreateConferenceSession(IUccEndpoint endpoint, string conferenceUri)
    {
      this.endpoint = endpoint;
      this.conferenceUri = conferenceUri;

      IUccSessionManager sessionManager = (IUccSessionManager)endpoint;

      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_CONFERENCE, null);
      confSession = (IUccConferenceSession)session;

      ComHelper.Advise<_IUccConferenceSessionEvents>(confSession, this);

      confSession.Enter(UccHelper.GetUri(conferenceUri), null);

    }

Esse código não tem segredo. Logo depois que o método “Enter” é chamado, o evento _IUccConferenceSessionEvents.OnEnter é chamado assincronamente.

    public void OnEnter(UccConferenceSession pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Nope");
            

      IUccConferenceSessionParticipant localParticipant = (IUccConferenceSessionParticipant)((IUccSession)pEventSource).LocalParticipant;
      UccConferenceSessionParticipantEndpoint partEndpoint = localParticipant.CreateParticipantEndpoint(this.endpoint.uccEndpoint.Uri, this.endpoint.uccEndpoint.Id, UCC_CONFERENCE_ENTITY_TYPE.UCCCET_INSTANT_MESSAGING, null);
      ComHelper.Advise<_IUccConferenceSessionParticipantEvents>(localParticipant, this);

      IUccMediaChannel channel = partEndpoint.CreateChannel(UCC_MEDIA_TYPES.UCCMT_MESSAGE, null);
      ((IUccMediaChannel)channel).PreferredMedia = (int)(UCC_MEDIA_DIRECTIONS.UCCMD_RECEIVE | UCC_MEDIA_DIRECTIONS.UCCMD_SEND);
      partEndpoint.AddChannel(channel);

      ((IUccSession)pEventSource).LocalParticipant.AddParticipantEndpoint((IUccSessionParticipantEndpoint)partEndpoint, null);
    }

No método OnEnter, checamos por erros e na sequencia criamos o endpoint local do participante. No endpoint é onde especificamos o tipo de mídia que irá interagir com a conferência. No nosso exemplo, usaremos instant message.

Vamos fazer um advise nos eventos de _IUccConferenceSessionParticipantEvents. Após a chamada de OnEnter, _IUccConferenceSessionParticipantEvents.OnParticipantAdded será chamado assincronamente.

    public void OnAddEndpoint(UccConferenceSessionParticipant pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Nope");

      IUccSessionManager sessionManager = (IUccSessionManager)this.endpoint.uccEndpoint;
      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_INSTANT_MESSAGING, null);

      string imMCUUri = confSession.Properties.get_Property((int)UCC_CONFERENCE_SESSION_PROPERTY.UCCCSPID_CONFERENCE_INSTANT_MESSAGING_URI).StringValue;

      IUccInstantMessagingSessionParticipant imParticipant = (IUccInstantMessagingSessionParticipant)session.CreateParticipant(
        UccHelper.GetUri(imMCUUri), null);

      ComHelper.Advise<_IUccInstantMessagingSessionParticipantEvents>(imParticipant, this);

      session.AddParticipant((IUccSessionParticipant)imParticipant);

      this.imSession = (IUccInstantMessagingSession)session;

      ComHelper.Advise<_IUccSessionEvents>(session, this);
    }

No método OnAddEndponit, vamos checar por erros e então criaremos uma sessão de instant messaging, da mesma forma que criamos na parte 5. A diferença é que o participante é a conference Uri.

Os mesmos métodos são usados para mandar e receber mensagens na conferência.

Outros métodos interessantes na interface _IUccInstantMessagingSessionParticipantEvents são: OnRemoveParticipant, OnAddParticipant e OnTerminate. Esses eventos são usados para controlar quando os usuários estão entrando ou saindo da conferência.

Convidando usuários para uma conferência

Os passos anteriores nos levaram a um estado muito interessante. Temos uma conferência criada e uma sessão de instant messaging apontando pra ela, mas como convidamos usuários para a conferência que acabamos de criar?

Vamos entrar nesse ambiente nebuloso.

Para permitir que um usuário entre na conferência, devemos enviar o conference Uri para ele. O único mecanismo disponível para fazer isso na UCC API é a “application session”. Application session é um tipo de sessão que permite que qualquer tipo de informação seja transferida entre os usuários. O Microsoft Office Communicator usa o mesmo mecanismo descrito aqui.

Primeiro vamos explicar como enviar o convite. Posteriormente veremos como receber o convite e tratar a application session.

Enviando o convite:


    public void InviteUser(IUccEndpoint endpoint, string userUri, string conferenceUri)
    {
      this.conferenceUri = conferenceUri;

      UccContext inviteContext = new UccContext();
      inviteContext.AddNamedProperty("ConfInvitee", userUri);

      IUccSessionManager sessionManager = (IUccSessionManager)endpoint;
      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_APPLICATION, inviteContext);

      UccUriManager uriManager = new UccUriManager();
      UccUri userUriUri = uriManager.ParseUri(userUri);

      IUccSessionParticipant participant = session.CreateParticipant(userUriUri, inviteContext);
      ComHelper.Advise<_IUccApplicationSessionParticipantEvents>(participant, this);
      session.AddParticipant(participant, null);
    }

Precisamos salvar o conferenceUri pois será usado posteriormente numa chamada assíncrona.

A idéia aqui é muito simples. Criamos uma sessão do tipo application (uma sessão para mandar informações arbitrárias), então criamos o participante com a Uri do usuário remoto. Então fazemos um advise na interface _IUccApplicationSessionParticipantEvents e adicionamos o participante na sessão.

O método _IUccApplicationSessionParticipantEvents.OnOutgoingInvitation será chamado:


    public void OnOutgoingInvitation(UccApplicationSessionParticipant pEventSource, UccOutgoingInvitationEvent pEventData)
    {
      pEventData.ContentType = "application/ms-conf-invite+xml";
      pEventData.SessionDescription =
        "<Conferencing version=\"2.0\"><focus-uri>" + this.conferenceUri + "</focus-uri><subject>AE Chat</subject><im available=\"true\"><first-im></first-im></im></Conferencing>";

      pEventData.Send();
    }

Por que todo esse XML inventado? Resp: Porque é o formato usado pelo Microsoft Office Communicator. POdemos mudar esse formato para qualquer outro, desde que seja uma convenção entre a aplicação cliente. O OCS Server e a UCC API somente propagam o que você preencher aqui.

Posteriormente, o método OnInvitationAccepted será chamado quando o usuário aceitar o convite.

Esse fluxo é olhando pela perspectiva do usuário que cria a conferência e convida os outros usuários. Quando o usuário entra na conferência, o método OnParticipantAdded será chamado.

Recebendo convites

Agora veremos a mesma lógica sob a ótica de quem está sendo convidado para a conferência.

O convite é enviado através de uma application session. A primeira pergunta é: Como a UCC API sabe que uma sessão que está sendo recebida (incoming session) é uma application session?

A resposta é: Ela não sabe. Você precisa dizer pra ela.

A mágica é:


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

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

      IUccSessionManager sm = (IUccSessionManager)uccEndpoint;
      sm.RegisterSessionDescriptionEvaluator(this);
      ComHelper.Advise<_IUccSessionManagerEvents>(sm, 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, transportMode);

      uccEndpoint.Enable(null);
    }

Esse código está no momento em que o endpoint é habilitado. O método RegisterSessionDescriptorEvaluator method força o objeto passado como parâmetro a implementar _IUccSessionDescriptionEvaluator.

Essa interface pede a implementação do seguinte método:

    public bool Evaluate(string bstrContentType, string bstrSessionDescription)
    {
      if (bstrContentType == "application/ms-conf-invite+xml")
        return true;
      else
        return false;
    }

Isso é complicadinho. Para cada incoming session, o método “Evaluate” será chamado. Se ele retornar “true”, estamos dizendo para a API que esta sessão é uma application session. Se ele retornar false, a UCC API vai tentar descobrir o tipo de sessão. Claro que a UCC API conhece os tipos default.

Quando o usuário recebe o convite, ele detecta que este content-type mapeia para uma application session então método “OnIncomingSession” é chamado:

    public void OnIncomingSession(IUccEndpoint pEventSource, UccIncomingSessionEvent pEventData)
    {
      if (pEventData.Session.Type == UCC_SESSION_TYPE.UCCST_APPLICATION)
      {
        pEventData.Accept();
        UccIncomingInvitationEvent inInvitation = (UccIncomingInvitationEvent)pEventData;
        
        TextReader tr = new StringReader(inInvitation.RemoteSessionDescription);
        XmlDocument inviteDoc = new XmlDocument();
        inviteDoc.Load(tr);

        // Get the conference Focus URI from the XML stream.
        string conferenceUri = inviteDoc.SelectSingleNode("Conferencing/focus-uri").InnerText;

        CreateConferenceSession(this, conferenceUri);
      }
    }

O código acima interpreta o XML e obtém o conferenceURI enviado através da application session. O CreateConferenceSession faz o mesmo explicado anteriormente no tópico “Criando uma Conference Session”.

Depois que o usuário cria a conference session, habilita o endpoint local ele consegue enviar e receber mensagens através da conference session.

Informações importantes sobre a Conferência

Quando estava desenvolvendo uma aplicação de chat sobre a UCC API, eu tive vários issues estranhos com a conferênica.

Algumas mensagens não estavam sendo roteadas para todos os usuários na conferência. Após uma semana lutando com este problema, reparei que o problema estava no content-type.

Quando o content-type da mensagem é diferente de text/plain, a mensagem não é roteada para todos os usuários. Eu não encontrei nenhuma informação sobre isso em nenhuma documentação oficial (ou forums).

Após fazer alguns sniffs bore os pacotes do Microsoft Office Communicator, reparei que o MOC manda todas as mensagens em text/plain e dentro do content-type manda a mensagem formata, codificada em Base64.

Código fonte

Aqui está todo o código fonte de todas as partes do tutorial: UCCApiSample. Está bastante bagunçado e confuso, mas mesmo assim decidi publicá-lo.

Conclusão

Existem muitas particularidades sobre a conferência na UCC API. Eu espero que este overview possa poupá-lo de grande parte do trabalho.

Infelizmente a falta de recursos sobre a UCC API na web não ajuda muito. Isso foi uma das minhas principais motivações para escrever tudo isso.

Esse é o último. Divirta-se.

UCC API – Unified Communications Client API – Parte 5 – Instant Messaging Session

Visão Geral

Nesta parte veremos como estabelecer uma sessão de instant messaging entre dois usuários na UCC API.

Uma informação importante sobre esse tipo de sessão é que ela tem apenas UM participante. É por isso que conferências são um pouco mais complicadas do que sessão “single-user”.

Eu vou falar nessa parte apenas de sessões para um único usuário.

Outra particularidade é que existem tratamentos diferentes para sessões originárias do usuário conectado (outgoing sessions) e sessões iniciadas por outros usuários (incoming session).

Outgoing Sessions

Iniciar uma sessão é simples:


    public void CreateIMSession(IUccEndpoint endpoint, string user)
    {
      IUccSessionManager sessionManager = (IUccSessionManager)endpoint;

      UccUriManager uriManager = new UccUriManager();      
      UccUri destUser = uriManager.ParseUri(user);

      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_INSTANT_MESSAGING, null);
      IUccSessionParticipant participant = session.CreateParticipant(destUser, null);
      ComHelper.Advise<_IUccInstantMessagingSessionParticipantEvents>(participant, this);     
      session.AddParticipant(participant);

      imSession = (IUccInstantMessagingSession)session;
      ComHelper.Advise<_IUccInstantMessagingSessionEvents>(imSession, this);     
    }

Esta função recebe um endpoint habilitado e o usuário que deseja convidar para a sessão. O formato esperado é “sip:user@domain.com”. Praticamente todo lugar que espera por uma Uri utiliza este formato na UCC API.

Na primeira linha, fazemos um cast do endpoin para IUccSessionManager. O session manager é o mecanismo para iniciar qualquer tipo de sessão na UCC API. O tipo de sessão é especificado no primeiro parâmetro do método CreateSession.

O próximo passo é instanciar um participante. É aqui que colocamos a Uri do usuário remoto. Então fazemos um advise na interface _IUccInstantMessagingSessionParticipantEvents no participante criado.

O último passo é fazer um advise da interface _IUccInstantMessagingSessionEvents no objeto imSession. O objeto imSession object é um objeto do tipo IUccInstantMessagingSession. Nós precisamos guardar uma referência para ele para podermos enviar mensagens.

A interface _IUccInstantMessagingSessionParticipantEvents tem métodos importantes como OnComposing e OnIdle, usados para podermos criar features do tipo “o usuário está digitando” numa aplicação de instant messaging. O OnInstantMessageReceived é o evento mais importante. Esse método será chamado toda vez que o participante remoto manda uma mensagem.

    public void OnInstantMessageReceived(UccInstantMessagingSessionParticipant pEventSource, UccIncomingInstantMessageEvent pEventData)
    {
      MessageBox.Show(pEventData.Content);
    }

É importante tratarmos o content-type da mensagem.

Para mandar uma mensagem, usamos o método SendMessage da IUccInstantMessagingSession.

    public void SendMessage(string msg)
    {
      imSession.SendMessage("text/plain", msg);
    }

Incoming Sessions

Toda vez que uma incoming session é iniciada, o método IUccEndpoint.OnIncomingSession é chamado (se o endpoint corretamente ouve os eventos de _IUccEndpointEvents).

Neste evento, é necessário tratarmos a sessão da seguinte forma:


    public void OnIncomingSession(IUccEndpoint pEventSource, UccIncomingSessionEvent pEventData)
    {
      if (pEventData.Session.Type == UCC_SESSION_TYPE.UCCST_INSTANT_MESSAGING)
      {
        pEventData.Accept();
        imSession = (IUccInstantMessagingSession)pEventData.Session;

        IUccSessionParticipant participant = (IUccSessionParticipant) pEventData.Inviter;
        ComHelper.Advise(participant, this);     
      }
    }

A idéia é muito parecida com uma outgoing session. A diferença é que recebemos o objeto IUccInstantMessageSession e o IUccInstantMessagingSessionParticipant do parâmetro pEventData.

Devemos manter a referência do objeto imSession para enviarmos mensagens através do método SendMessage.

Ouvindo os eventos de IUccInstantMessagingSessionParticipantEvents, teremos acesso ao evento OnInstantMessageReceived.

Desta forma, podemos estabelecer o canal de comunicação para incoming sessions.

UCC API – Unified Communications Client API – Parte 4 – Containers

Overview

No post anterior, eu expliquei a idéia por trás da publicação de category instances. Nesse parte vamos ver como podemos controlar para quais usuários a informação é publicada.

Essa feature funciona para qualquer tipo de category instance. Está relacionada com publicar informações diferentes para diferentes grupos de usuários.

O Container

O container é uma especialização de uma category instance. Isso significa que adicionar ou remover containers é feito através do processo de publicação de category instances.

O container tem a idéia de armazenar informações de publicação para um conjunto de usuários. O conjunto de usuários é definido por: usuários do mesmo domínio, mesma empresa, “federated enterprise”, publico ou todos, ou ainda construindo o grupo usuário a usuário.

Geralmente a organização dos containers vem do mais permissivo com Id’s mais baixos e os mais restritivos com Id’s mais altos.

Uma boa organização de containers seria:

  • ContainerID 0: Everyone
  • ContainerID 100: Federated enterprise
  • ContainerID 200: Company
  • ContainerID 32000: Blocked

O Microsoft Office Communicator tem uma organização similar. A lista completa está aqui: Microsoft Office Communicator Presence Categories

Se você está escrevendo o seu próprio cliente baseado na UCC API, você precisa criar a organização inicial dos containers, publicando as category instances como “static” (para que o servidor as persista). O Microsoft Office Communicateor faz a configuração inicial no seu primeiro login.

Como os direitos no container são verificados

Para publicar as informações de categorias usando listas de controle de acesso (ACL ou access control lists), o servidor OCS segue a seguinte lógica:

  • O servidor começa a publicação dos containers mais restritivos para os mais permissivos. Exemplo: Se um usuário está no container “Blocked” (32000), este usuário irá receber as informações publicadas no container 32000.
  • Se existe informação publicada no container, essa informação é enviada.
  • Se não existe informação publicada no container mais restritivo, a informação do container mais permissivo é publicada.

Vou mostrar alguns exemplos para ilustrar essa idéia. Todos os exemplos seguem a configuração de container mostrada anteriormente.

Example 1:

  • Usuário Paul não tem o usuário Mary como membro de nenhum container.
  • Usuário Paul tem o usuário John como membro do seu container 32000 (blocked users)
  • Usuário Paul publica o estado “Available” nos containers 2 e 3.
  • Usuário Paul publica o estado “Offline” no conatiner 32000.
  • Usuário Mary recebe o estado “Available” do usuário Paul. Isso acontece porque Mary cai na regra “Everyone” na configuração de containers.
  • Usuário John recebe o estado “Offline” do Paul.

Example 2:

  • Usuário Paul não tem Mary como membro de nenhum container.
  • Usuário Paul tem John como membro do container 32000 (blocked users)
  • Usuário Paul publica o estado “Available” nos containers 2 and 3.
  • Usuário Mary recebe o estado “Available” do Paul. Isto acontece porque Mary cai na regra “Everyone” da configuração de containers.
  • Usuário John recebe o estado “Available” do Paul. Isto acontece porque nenhum estado foi publicado no container 32000. Quando isso acontece a informação mais permissiva é publicada.

Esta regra aplica-se para todo e qualquer tipo de category instance. Informações de contato, por exemplo, segue a mesma regra. Você pode publicar mais informações para usuários mais confiáveis e menos informações para usuários que você não confia.

Especificamente para a feature de bloqueio de usuários, esse é o jeito certo de fazer. Nâo existe uma feature especifica para bloquear usuários, exceto esta de “remover permissão de ver o estado “disponível” para o usuário.

Conclusão

Para evitar problemas as listas de controle de acesso (ACL’s) da UCC API devem ser corretamente configuradas, especificamente os containers e membros dos containers. Sempre que ocorre uma publicação, é importante publicar a informação mais restritiva e mais permissiva em cada um dos seus containers (Ex.: Estado disponível e offline).

Infelizmente a documentação do MSDN não é muito clara sobre este assunto, especificamente a idéia de ter que publicar a informação mais restritiva. Em outras palavras, o estado “Offline” no exemplo.