Docstoc

Google Data APIs Calendar 2

Document Sample
Google Data APIs Calendar 2 Powered By Docstoc
					                                                                                                         1/26



Google Documents List Data API
Integrando aplicações Java com o Google Docs
Aprenda a integrar sua aplicação Java com a API de dados do Google Documents

PAULO CÉSAR COUTINHO
  Como é do conhecimento de muitos, o Google disponibiliza APIs que permitem aplica ções
terceiras interagirem com os seus vários serviços. Dentre essas APIs estão as APIs de dados 1
(Google Data APIs), através das quais é possível interagir com serviços 2 como Google Calendar3,
Google Contacts4 (contatos do GMail), Picasa Web, Google Finance, Blogger, You Tube, Google
Health, Google Docs, dentre outros.
  Neste artigo veremos como utilizar a Google Documents List Data API, que é responsável pela
interação com o serviço do Google Documents (ou simplesmente Google Docs). Aqui
aprenderemos a realizar algumas operações básicas, como recuperar a lista de arquivos do usuário,
criar um novo arquivo, fazer upload de um arquivo, editar os meta-dados de um arquivo, alterar o
conteúdo de um arquivo e remover um arquivo no servidor. Também veremos alguma s operações
especiais como converter um texto no formato de imagem (uma página escaneada de um livro, por
exemplo) num documento de texto editável, e traduzir um documento para um outro idioma.
  Veremos como cada operação funciona tanto a nível de protocolo, onde serão mostrados as
requisições enviadas e as respostas recebidas em forma de XML, quanto a nível e código, utilizando
a Java Client Library. Para termos um foco mais prático, construiremos, como projeto final deste
artigo, um conversor de arquivos capaz de fazer diversos tipos de conversão (doc para pdf ou ods
para xls, por exemplo) incluindo a conversão de arquivos jpg, gif ou png para texto e tradução
automática de documentos.


A API
 A Google Documents List Data API, bem como as outras APIs de dados, disponibiliza métodos
que permitem aplicações de terceiros interagirem com algum serviço Google, no nosso caso, o
Google Docs. Por meio desta, é possível listar, fazer download e upload de documentos em
formatos diversos, editar as listas de controle de acesso para documentos, versionar documentos,
dentre outras coisas.
 No momento da escrita deste artigo, a API está na versão 2.0, mas a versão 3.0 já está em
desenvolvimento no Google Labs e já é em parte suportada pela versão mais recente da Java Client
Library. Entre as novas features estão: Compartilhamento de pastas; tradução de documentos e
reconhecimento de texto ou OCR (Optical Character Recognition) para os formatos gif, jpg e png.
 Para trabalhar com a API de Lista de Documentos, utilizamos basicamente o feed do tipo
DocumentListFeed e entradas do tipo DocumentListEntry ou suas subclasses, como DocumentEntry,
PdfEntry, PresentationEntry, FolderEntry, SpreadsheetEntry e PhotoEntry. Para lidar com as listas de
controle de acesso são utilizados o feed do tipo AclFeed e entradas do tipo AclEntry.



 1
         O Quadro “Google Data APIs – Conceitos Principais” mostra os conceitos básicos utilizados pelas APIs de
Dados. Você também pode encontrar mais detalhes sobre as APIs de Dados Google na edição 64 da Java Magazine no
artigo “Google Data APIs”.
 2
         A lista completa dos serviços suportados pode ser vista em http://code.google.com/apis/gdata/
 3
         O artigo “Google Data APIs: Calendar 2.0”, da Java Magazine 67, mostra com detalhes o uso da API de
Dados do Google Calendar.
 4
         O artigo “Google Data APIs: Contacts”, da Java Magazine 65, mostra como utilizar a API de Dados de
Contatos.
                                                                                                             2/26


Google Calendar Data API
 A Google Calendar Data API (API de Dados de Agenda Google), assim como as demais APIs de
dados, provê ao desenvolvedor um conjunto de métodos responsáveis por permitir a interação entre
uma aplicação terceira e um serviço Google, nesse caso o Google Calendar (Google Agenda, aqui
no Brasil). Com ela podemos, dentre outras coisas: gerenciar a lista de calendários de um
determinado usuário; gerenciar os eventos pertencentes a esses calendários, bem com os
comentários relacionados a cada evento; inscrever o usuário nos calendários públicos de outros
usuários; compartilhar seus calendários com outros usuários e também configurar lembretes para os
eventos. Um único artigo é pouco para descrever e mostrar exemplos de todas essas
funcionalidades, mas tentaremos aqui explorar as principais delas.
 Na API de Agenda lidamos com quatro tipos diferentes de feed e, conseqüentemente, de entradas
(entries). A Tabela 1 mostra a descrição de cada um desses feeds e também as classes
correspondentes na Java Client Library. Para não tornar o artigo muito extenso, analisaremos
apenas os feeds de agendas, eventos e comentários.


                                                                                                Classes da
        Feed                                    Descrição
                                                                                                Biblioteca
                     Calendários do usuário. Inclui tanto os calendários
                                                                                              CalendarFeed e
      Calendars      criados pelo usuário como os demais aos quais este têm
                                                                                              CalendarEntry
                     algum tipo de acesso.
                                                                                          CalendarEventFeed e
        Events       Eventos de um determinado calendário.
                                                                                          CalendarEventEntry

      Comments       Comentário de um determinado evento.                                      Feed e Entry5

                     Nível de acesso dos usuários em relação a um calendário
     ACL (Access
                     (owner, read, etc). Pode ser alterado pelo(s) owner(s) do             AclFeed e AclEntry
     Control List)
                     calendário.
 Tabela 1. Tipos de feed utilizados pela API de Agenda Google.

Autenticação
 Nos artigos anteriores vimos as diferentes formas de autenticação suportadas pelas APIs de dados
Google: AuthSub, Client Login (utilizada em nossos exemplos) e OAuth. No entanto, a API de
Agenda oferece suporte a um tipo de autenticação especial, através da qual não precisamos
informar, em momento algum, as credenciais do usuário. Esse tipo de autenticação é chamado de
Magic Cookie Authentication, que como o próprio nome sugere é baseado num “cookie mágico”,
ou seja, uma chave única gerada pelo servidor que pode ser usada para ter acesso direto à lista de
eventos de um determinado calendário. A URL do feed utilizando esse tipo de autenticação se
parece com a seguinte:

http://www.google.com/calendar/feeds/<calendarId>/private-<magicCookie>/basic

 Para obter essa URL mágica, siga os passos abaixo:
    1. Acesse o site do Google Calendar (http://calendar.google.com);



 5
          Até a versão corrente (v1.28) durante a escrita deste artigo, a Java Client Library não oferece um suporte
direto a feeds ou entradas de comentários de eventos. Porém conseguimos obter esses dados por meio da API genérica
da biblioteca com o uso de extensions.
                                                                                                                3/26


     2. Na lista de agendas à esquerda, escolha a agenda com a qual deseja obter a URL de acesso,
        clique na seta que se encontra ao lado do nome da agenda e escolha a opção “Calendar
        Settings”;
     3. Na tela de configurações da agenda, vá até a seção Private Access e clique no botão XML
        (ver Figura 1) que o endereço será exibido.

 Copie o endereço e cole num browser de sua preferência para verificar que se trata de um feed
válido. Perceba ainda que o URL padrão acaba com a palavra basic, que indica o valor de projeção
do feed. Esse valor pode ser substituído por algum dos demais valores de projeção disponíveis,
como full. Mais adiante falaremos com mais detalhes dos valores de projeção suportados.




 Figura 1. Tela para obtenção do magic cookie.



Calendar Feeds
  Iniciaremos analisando os feeds de agenda. Podemos recuperar a lista de agendas do usuário de
três maneiras. Vejamos cada uma dessas maneiras:

 metafeed – Feed privado somente para leitura que contém a lista de calendários aos quais o
 usuário tem acesso. É acessado através da seguinte URL:

 http://www.google.com/calendar/feeds/default

 allcanlendars – Feed privado para leitura e escrita usado para subscrições e personalizações das
 agendas do usuário. É acessado pela seguinte URL:

 http://www.google.com/calendar/feeds/default/allcalendars/full

 owncalendar – Feed privado para leitura e escrita usado para gerenciar as agendas pertencentes ao
usuário, ou seja, as agendas sobre as quais o usuário tem a permissão de owner. Um usuário se torna
o owner de um calendário quando este é criado por ele ou quando outro usuário (que já é owner)
concede tal permissão. Note que esse feed só retorna as agendas gerenciadas pelo usuário. É
acessado pela seguinte URL:

 http://www.google.com/calendar/feeds/default/owncalendars/full


Operações como insert() e delete() apresentam comportamentos diferentes de acordo com o tipo de feed utilizado.
Quando utilizamos o feed owncalendars. insert() e delete() servem para criar e apagar uma entrada de agenda no feed.
No caso do delete() o usuário precisar ser owner da agenda.Já quando estamos utilizando o feed allcalendars, insert() e
delete() são responsáveis por inscrever o usuário num calendário público já existente e cancelar a inscrição,
respectivamente Nesse caso, o calendário deve possuir um ID válido mesmo quando fizermos um insert().
                                                                                                   4/26


 A Listagem 1 mostra como se parece uma entrada de agenda. A maioria dos elementos já foi vista
nos artigos anteriores. Temos os elementos básicos do Atom e Atom Publishing Protocol, prefixados
com o namespace atom e app, respectivamente; elementos do GData Protocol, com o namepace gd
e os elementos específicos da Calendar API, com o prefixo gCal. A Tabela 2 mostra os principais
elementos do namespace gCal. Para a lista completa com a descrição detalhada dos valores
possíveis, consulte a documentação oficial (ver seção Links).


Na API de Agenda, o elemento <atom:author> é obrigatório.


Listagem 1. Exemplo de uma Entrada de Agenda.
<atom:entry>
   <atom:id>http://www.google.com/calendar/feeds/default/calendars/user%40gmail.com</atom:id>
     <atom:published>2008-12-19T01:21:13.889Z</atom:published>
     <atom:updated>2008-12-11T05:12:51.000Z</atom:updated>
     <app:edited xmlns:app='http://www.w3.org/2007/app'>2008-12-11T05:12:51.000Z</app:edited>
     <atom:title type='text'>Minha Agenda</atom:title>
     <atom:summary type='text'>Descrição da Agenda...</atom:summary>
     <atom:content type='application/atom+xml'
         src='http://www.google.com/calendar/feeds/user%40gmail.com/private/full'/>
     <atom:link rel='alternate' type='application/atom+xml'
         href='http://www.google.com/calendar/feeds/user%40gmail.com/private/full'/>
     <atom:link rel='http://schemas.google.com/acl/2007#accessControlList'
         type='application/atom+xml'
         href='http://www.google.com/calendar/feeds/user%40gmail.com/acl/full'/>
     <atom:link rel='self' type='application/atom+xml'
         href='http://www.google.com/calendar/feeds/default/owncalendars/full/user%40gmail.com'/>
     <atom:link rel='edit' type='application/atom+xml'
         href='http://www.google.com/calendar/feeds/default/owncalendars/full/user%40gmail.com'/>
     <atom:author>
       <atom:name>Fulano da Silva</atom:name>
     </atom:author>
     <gCal:timezone xmlns:gCal='http://schemas.google.com/gCal/2005' value='America/Recife'/>
     <gCal:timesCleaned xmlns:gCal='http://schemas.google.com/gCal/2005' value='0'/>
     <gCal:hidden xmlns:gCal='http://schemas.google.com/gCal/2005' value='false'/>
     <gCal:color xmlns:gCal='http://schemas.google.com/gCal/2005' value='#A32929'/>
     <gCal:selected xmlns:gCal='http://schemas.google.com/gCal/2005' value='true'/>
     <gCal:accesslevel xmlns:gCal='http://schemas.google.com/gCal/2005' value='owner'/>
     <gd:where xmlns:gd='http://schemas.google.com/g/2005' valueString='Brasil'/>
   </atom:entry>




       Elemento                      Tipo                              Descrição
                                                ID do fuso-horário da agenda. Ex.: “America/Recife” ,
        timezone                     String
                                                “América/Los_Angeles”

          hidden                  Booleano      Indica se a agenda está visível ou não.

           color                     String     Cor da agenda. (Ex.: “#5229A3”);

         selected                 Booleano      Indica se o calendário está selecionado ou não.

                                                Indica o nível de acesso do usuário logado sobre o
      accessLevel                    String     calendário. Os valores válidos são: none, read, freebusy,
                                                editor, owner e root.
 Tabela 2. Principais elementos do namespace.gCal.
                                                                                                             5/26


  Agora vamos dar início à implementação das novas funcionalidades do nosso organizador pessoal.
Começaremos pelo gerenciamento das agendas do usuário. As Listagens 2 e 3 mostram o código
das classes Calendar e CalendarServiceManager.
  A classe Calendar é apenas um bean simples que representará uma Agenda no domínio da nossa
aplicação. Nossa agenda possui um ID, um título, uma descrição, um autor e uma indicador para
dizer se o usuário é owner da agenda ou não.
  Já a classe CalendarServiceManager, que ainda não está completa6, é o coração da nossa integração
com a API de Agenda. Ela será capaz de interagir com os serviços providos pela Google Calendar
Data API e converter os tipos desse serviço nos tipos específicos da nossa aplicação. Essa classe
estende de AbstractServiceManager7, que provê as funciolidades básicas, como autenticação, para
nossos gerenciadores de serviço. Iniciamente, temos a declaração de algumas constantes que
representam as URLs e outras strings que utilizaremos em nosso exemplo. O construtor privado, o
atributo INSTANCE e o método getInstance() caracterizam nossa classe como um singleton. O
atributo service serve para armazenar uma instância da classe de serviço CalendarService, provida
pela Java Client API. Nossa classe também provê uma implementação para o método
createService(), abstrato em AbstractServiceManager. Essa implementação simplesmente cria um
CalendarService, armazena sua referência e retorna o objeto criado. O método parseCalendar() é
responsável por converter um CalendarEntry, que faz parte da API, no nosso objeto Calendar. Para
isso simplesmente copiamos as propriedades de um objeto para o outro, fazendo as devidas
conversões. Os métodos getTitle() e getSummary() de CalendarEntry retornam um objeto
TextConstruct, sendo assim, é necessário invocar o método getPlainText() para obter o valor no
formato String. O método CalendarEntry.getAuthors() retorna uma lista de Person, porém para nossa
agenda, pegamos apenas o nome do primeiro autor da lista. Para saber se o usuário é owner da
agenda usamos o método CalendarEntry.getAccessLevel() e comparamos com a constante
AccessLevelProperty.OWNER. O método buildCalendarEntry() faz o papél inverso. Este recebe um
CalendarEntry, que será populado, e um Calendar, de onde os dados serão obtidos. No nosso
exemplo estamos copiando apenas as propriedades title e description, uma vez que serão são as
propriedades que atualizaremos. O método getUpdatedEntry() recupera a entrada atualizada para
uma dada agenda e feed. Primeiramente nos autenticamos no serviço, caso ainda não o tenhamos
feito, através do método authenticate(), herdado de AbstractServiceManager.

Listagem 2. Classe Calendar, representa uma agenda do usuário.
// declaração de pacotes e imports omitidos.
public class Calendar {

        private   String id;
        private   String title;
        private   String description;
        private   String author;
        private   boolean owned;

        // Gets e Sets

        @Override
        public String toString() {
          return title;
        }
}


Listagem 3. Classe CalendarServiceManager, reponsável por interagir com a API de Agenda Google.
// declaração de pacotes e imports omitidos…
public class CalendarServiceManager extends AbstractServiceManager {
  private static final String OWN_CALENDARS_FEED_URL =
      "http://www.google.com/calendar/feeds/default/owncalendars/full";

    6
        A classe CalendarServiceManager será incrementada ao decorrer deste artigo.
    7
        A classe AbstractServiceManager foi vista no artigo anterios e está disponível para download no site da Java
Magazine.
                                                                                                   6/26


    private static final String ALL_CALENDARS_FEED_URL =
        "http://www.google.com/calendar/feeds/default/allcalendars/full";
    private static final String EVENTS_FEED_BASE_URL =
        "http://www.google.com/calendar/feeds/#CalendarId#/private/full";

    private static final CalendarServiceManager INSTANCE = new CalendarServiceManager();
    private CalendarService service;

    private CalendarServiceManager() {}

    public static CalendarServiceManager getInstance() {
      return INSTANCE;
    }

    @Override
    protected GoogleService createService() {
      service = new CalendarService(Constants.APPLICATION_NAME);
      return service;
    }

    private Calendar parseCalendar(CalendarEntry entry) {
      Calendar calendar = new Calendar();
      calendar.setId(entry.getId());
      calendar.setTitle(entry.getTitle().getPlainText());
      if (entry.getSummary() != null) {
        calendar.setDescription(entry.getSummary().getPlainText());
      }
      if ((entry.getAuthors() != null) && (entry.getAuthors().size() > 0)) {
        calendar.setAuthor(entry.getAuthors().get(0).getName());
      }
      calendar.setOwned(entry.getAccessLevel().equals(AccessLevelProperty.OWNER));
      return calendar;
    }

    private void buildCalendarEntry(CalendarEntry entry, Calendar calendar) {
      entry.setTitle(new PlainTextConstruct(calendar.getTitle()));
      entry.setSummary(new PlainTextConstruct(calendar.getDescription()));
    }

    private CalendarEntry getUpdatedEntry(Calendar calendar, String feedUrl) {
      CalendarEntry entry = null;

        try {
          authenticate();
          String urlString = feedUrl + "/" + getShortCalendarId(calendar.getId());
          URL entryUrl = new URL(urlString);
          entry = service.getEntry(entryUrl, CalendarEntry.class);
        } catch (Exception e) {
          e.printStackTrace();
        }
        return entry;
    }

    private String getShortCalendarId(String fullId) {
      return fullId.replaceAll(
          "http://www.google.com/calendar/feeds/default/calendars/", "");
    }
}




        Elemento                             Descrição                                  Editável

         public       Mostra apenas os eventos públicos.                             Somente Leitura
                                                                                                              7/26


                                                                                           Editável apenas por
       private       Mostra os eventos públicos e privados.
                                                                                           usuários autorizados
       private-
                     Mostra os eventos públicos e privados.                                   Somente leitura
     magicCookie
 Tabela 1. Opções de visibilidade suportadas pela API de Agenda Google.



      Elemento                                  Descrição                                         Editável
                     Inclui todas as propriedades do evento, porém os
                     comentários não são incluídos inline8 e sim como um
         full                                                                                 Leitura e escrita
                     link para um feed separado através do elemento
                     <gd:feedLink>.
         full-       O mesmo que full, entretando sem incluir os elementos
                                                                                              Leitura e escrita
     noattendees     do tipo <gd:who>.

      composite                                                                               Somente leitura

                     Inclui apenas as informações básicas sobre o evento,
 attendees-only                                                                               Somente leitura
                     inclusive o elemento <gd:who>
                     Inclui apenas as informações básicas sobre o evento,
      free-busy                                                                               Somente leitura
                     incluindo o elemento <gd:when>

        basic        Inclui apenas as informações básicas de um feed Atom.                    Somente leitura
 Tabela 2. Opções de projeção suportadas pela API de Agenda Google.

 O principal tipo de entrada com o qual iremos interagir na API de Agenda são as entradas do tipo
Evento (Event Kind). Cada entrada de contato pode conter: um ou mais elementos <gd:who>,
representando as pessoas associadas ao evento, onde cada qual é represen ta por uma entrada de
contato (Contact Kind); Um elemento <gd:comments> que referencia um feed contendo elementos
do tipo Mensagem (Message Kind) e um ou mais elementos <gd:extendedProperty> para armazenar
os dados customizados da aplicação.


 disponibiliza métodos para que uma aplicação terceira possa acessar e gerenciar os dados
referentes aos contatos e grupos de contatos de um determinado usuário. Como vimos no artigo
anterior, o protocolo GData é baseado principalmente no Atom, que por sua vez tem como
principais elementos o Feed e o Entry. Vimos também que o conteúdo do elemento Entry pode
variar de acordo com o tipo de entrada que desejamos representar.
 Agora veremos com mais detalhes as entradas que representam contatos e grupos. A Tabela 1
mostra 9 os principais elementos que representam uma Entrada de Contato 10. Note que os elementos
padrão do Atom possuem o namespace atom precedendo seus nomes, enquanto os elementos
específicos da API possuem os devidos namespaces (gd e gContact). Note que as multiplicidades


 8

 9
         Aqui iremos mostrar apenas os elementos que são filhos imediatos de Entry. Porém, alguns desses elementos
possuem seus próprios sub-elementos. Para uma referência completa consulte a documentação oficial, ver Links.
 10
         Ao decorrer do artigo utilizaremos os termos Entrada de Contato e Entrada de Grupo para representar os dados
correspondentes a um contato ou grupo específicos no servidor.
                                                                                                             8/26


dos elementos são representadas pelos símbolos “*” e “?” 11, seguindo o mesmo padrão utilizado em
expressões regulares, DTD, etc. O símbolo “?” após o nome do elemento indica que este pode
aparecer “zero ou uma” vez(es), enquanto o símbolo “*” indica que o elemento pode aparecer “zero
ou muitas” vezes. Caso nenhum símbolo esteja presente ao final do nome do elemento, este é
obrigatório e deve aparecer uma única vez.

           Elemento                                                 Descrição

           atom:id?               Identificador único do contato. Criado pelo servidor.

         atom:updated?            Data e hora da última atualização. Atualizada pelo servidor.

                                  Indica o tipo de entrada, Contato nesse caso. Possui o valor:
         atom:category*           <atom:category      scheme="http://schemas.google.com/g/2005#kind"
                                  term="http://schemas.google.com/g/2008#contact"/>

           atom:title             Nome do contato.

         atom:content?            Notas / observações sobre o contato.

                                  Links para informações relacionadas ao contato. O tipo de relação é
                                  definido através do atributo rel. Exemplos de links são: Imagem do
           atom:link*
                                  contato, página HTML com a descrição do contato, endereço para
                                  edição do contato, etc.
                                  Informações sobre as organizações onde o contato trabalha, como
        gd:organization*
                                  nome da organização e função exercida.

                                  Endereços de e-mail do contato. O tipo de e-mail (pessoal, trabalho,
           gd:email*
                                  etc.) é definido através do atributo rel.
                                  Endereços de Instant Messaging (ex.: MSN, GTalk, AIM) do
             gd:im*
                                  contato. O provedor utilizado é definido através do atributo protocol.
                                  Números de telefone do contato. O tipo de telefone (residencial,
       gd:phoneNumber*
                                  trabalho, celular, etc.) é definido através do atributo rel.

                                  Endereços do contato. O tipo de endereço (residencial, trabalho,
       gd:postalAddress*
                                  etc.) é definido através do atributo rel.
                                  Propriedades estendidas (específicas de uma determinada aplicação)
                                  do contato. Por exemplo: Uma aplicação A pode definir que um
      gd:extendedProperty*        contato deve possuir uma propriedade “Número de Filhos”, que não
                                  faz parte dos atributos padrão. Nesse caso, essa propriedade deve ser
                                  representada pelo elemento extendedProperty.
 gContact:groupMembershi
                                  Grupos dos quais o contato faz parte.
          pInfo*



 11
        A documentação não cita nenhum caso de multiplicidade “um ou muitos”, ou seja, o elemento deve aparecer
pelo menos uma vez, mas pode aparecer várias vezes. Esse tipo de multiplicidade seria representado pelo símbolo “+”.
                                                                                              9/26



           gd:deleted?                  Se presente, indica que o contato foi deletado.

 Tabela 1. Elementos de uma entrada que representa um Contato.

 A Listagem 1 mostra um exemplo de XML representando um contato. Em contrapartida, a Java
Client Library nos fornece classes correspondentes a cada elemento deste XML. Por exemplo, a
classe com.google.gdata.data.contacts.ContactEntry representa uma Entrada de Contato. Veremos
essa classe com mais detalhes nos exemplos que iremos analisar.

Listagem 1. Exemplo de uma Entrada de Contato.
<atom:entry xmlns:atom='http://www.w3.org/2005/Atom'>
  <atom:id>
    http://www.google.com/m8/feeds/contacts/pcmnac.jm%40gmail.com/base/2ab854f0b592dda
  </atom:id>
  <atom:updated>2008-11-05T23:29:04.998Z</atom:updated>
  <atom:category scheme='http://schemas.google.com/g/2005#kind'
      term='http://schemas.google.com/contact/2008#contact'/>
  <atom:title type='text'>João da Silva</atom:title>
  <atom:content type='text'>Observações sobre esse contato</atom:content>
  <atom:link rel='http://schemas.google.com/contacts/2008/rel#edit-photo' type='image/*'
      href='http://www.google.com/m8/feeds/photos/media/pcmnac.jm%40gmail.com/2ab854f0b592dda/tGg'/>
  <atom:link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
      href='http://www.google.com/m8/feeds/photos/media/pcmnac.jm%40gmail.com/2ab854f0b592dda'/>
  <atom:link rel='self' type='application/atom+xml'
      href='http://www.google.com/m8/feeds/contacts/pcmnac.jm%40gmail.com/full/2ab854f0b592dda'/>
  <atom:link rel='edit' type='application/atom+xml'
      href='http://www.google.com/m8/feeds/contacts/pcmnac.jm%40gmail.com/full/2ab854f0b592dda/12'/>
  <gd:organization xmlns:gd='http://schemas.google.com/g/2005' primary='false'
      rel='http://schemas.google.com/g/2005#other'>
    <gd:orgName>Empresa A</gd:orgName>
    <gd:orgTitle>Analista</gd:orgTitle>
  </gd:organization>
  <gd:organization xmlns:gd='http://schemas.google.com/g/2005' primary='true'
      rel='http://schemas.google.com/g/2005#work'>
    <gd:orgName>Empresa B</gd:orgName>
    <gd:orgTitle>Diretor</gd:orgTitle>
  </gd:organization>
  <gd:email xmlns:gd='http://schemas.google.com/g/2005'
      rel='http://schemas.google.com/g/2005#home' address='contato1@gmail.com' primary='true'/>
  <gd:im xmlns:gd='http://schemas.google.com/g/2005' address='contato1@msn.com'
      label='CUSTOM' primary='false' protocol='http://schemas.google.com/g/2005#MSN'/>
  <gd:phoneNumber xmlns:gd='http://schemas.google.com/g/2005'
      rel='http://schemas.google.com/g/2005#mobile'>
    (66) 7777-8888
  </gd:phoneNumber>
  <gd:phoneNumber xmlns:gd='http://schemas.google.com/g/2005'
      rel='http://schemas.google.com/g/2005#home'>
    (12) 1234-2134
  </gd:phoneNumber>
  <gd:postalAddress xmlns:gd='http://schemas.google.com/g/2005'
      rel='http://schemas.google.com/g/2005#home'>
    Rua João e Maria N°1000, Recife-PE
  </gd:postalAddress>
  <gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005'
    name='Job' value='STUDENT'/>
</atom:entry>


 Vamos agora analisar os elementos que representam uma Entrada de Grupo de contatos. A
Tabela 2 nos mostra esses elementos e a Listagem 2 mostra um exemplo de XML. Assim como no
caso da Entrada de Contato, a Java Client Library possui uma classe para representar os dados de
um grupo, a classe com.google.gdata.data.contacts.ContactGroupEntry.

            Elemento                                                 Descrição
                                                                                                                 10/26



             atom:id?                  Identificador único do grupo. Criado pelo servidor.

         atom:updated?                 Data e hora da última modificação. Atualizada pelo servidor.

                                       Indica o tipo de entrada, Grupo nesse caso. Possui o valor:
         atom:category*                <atom:category      scheme="http://schemas.google.com/g/2005#kind"
                                       term="http://schemas.google.com/g/2008#group"/>

            atom:title                 Nome do grupo.

                                       Nome do grupo. Não pode ser modificado diretamente. As
          atom:content                 alterações feitas no elemento atom:title são automaticamente
                                       refletidas neste elemento.

            atom:link*                 Links para informações relacionadas ao Grupo.

                                       Propriedades estendidas (específicas de uma determinada aplicação)
      gd:extendedProperty*
                                       do grupo.

           gd:deleted?                 Se presente, indica que o grupo foi deletado.


   gContact:systemGroup?               Identificador para diferenciar os grupos do sistema.

 Tabela 2. Elementos de uma entrada que representa um Grupo.

Listagem 2. Exemplo de uma Entrada de Grupo.
<atom:entry xmlns:atom='http://www.w3.org/2005/Atom'>
  <atom:id>
    http://www.google.com/m8/feeds/groups/pcmnac.jm%40gmail.com/base/52e9cac309780704
  </atom:id>
  <atom:updated>2008-11-06T00:04:09.630Z</atom:updated>
  <atom:category scheme='http://schemas.google.com/g/2005#kind'
      term='http://schemas.google.com/contact/2008#group'/>
  <atom:title type='text'>Família</atom:title>
  <atom:content type='text'>Família</atom:content>
  <atom:link rel='self' type='application/atom+xml'
      href='http://www.google.com/m8/feeds/groups/pcmnac.jm%40gmail.com/full/52e9cac309780704'/>
  <atom:link rel='edit' type='application/atom+xml'
      href='http://www.google.com/m8/feeds/groups/pcmnac.jm%40gmail.com/full/52e9cac309780704/122'/>
  <gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005'
      name='Description' value='descrição qualquer'/>
</atom:entry>


 De um modo geral, para poder recuperar as informações de um feed ou de uma entrada (entry)
específica, é necessário conhecer as URLs dos recursos com os quais estamos lidando. Nas APIs de
dados Google, as URLs utilizadas para recuperar os feeds de contatos seguem o seguinte formato:

http://www.google.com/m8/feeds/contacts/userId/projection

 Onde userId representa o e-mail12 do usuário do qual os contatos devem ser recuperados e
projection indica a forma como os dados devem ser recuperados (a seguir veremos as opções
disponíveis). No caso dos feeds de grupos as URLs seguem o formato:


 12
          Pode-se usar o valor default no lugar do e-mail para recuperar os dados referentes ao usuário autenticado.
                                                                                                               11/26


http://www.google.com/m8/feeds/groups/userId/projection

 Os valores de projeção permitidos na API de Contatos são os seguintes:
     thin – Nenhuma propriedade estendida (elemento gd:extendedProperty) é retornada ou
        atualizada;
     property-KEY – KEY deve ser substituído pelo valor da chave de uma propriedade que deve
        ser incluída nas pesquisas e atualizações. Se você deseja que seja incluída uma propriadade
        de nome Job, por exemplo, use property-Job como valor de projeção. Se essa propriedade
        não estiver presente numa operação de atualização, esta será removida no servidor;
     full – Todas as propriedades estendidas são retornadas nas pesquisas e incluídas nas
        atualizações. Se alguma propriedade estendida existente não estiver presente numa
        operação de atualização, esta será removida no servidor.

 Assim como foi visto no artigo anterior, podemos usar queries para recuperar os dados de acordo
com os critérios que definirmos. A Tabela 3 mostra as opções de parâmetros que podem 13 ser
utilizados numa query para listar contatos. Os parâmetros marcados com “*” são específicos 14 da
API de Contatos. Todos os parâmetros da Tabela 3, exceto group, também são válidos para grupos.

      Parâmetro                                                  Descrição
                           Usado para alterar o formato da resposta. Os valores válidos são: atom (o
           alt
                           padrão), rss e json.
                           Número máximo de entradas a serem retornadas. Para recuperar todos os
      max-results          contatos, use um valor grande o suficiente (atualmente o valor padrão é
                           25).
                           Índice (iniciando em 1) do primeiro registro a ser retornado. Utilizado para
       start-index
                           paginação.

      updated-min          Seleciona apenas os contatos que foram atualizados após a data informada.

        orderby*           Critérios de ordenação. Atualmente a única opção é lastmodified.

                           Inclui os contatos deletados no feed. Quando um contato é deletado, sua
      showdeleted*         entrada permanece no servidor por 30 dias. Os valores válidos são true e
                           false.
                           Especifica a direção da ordenação. Os valores aceitos são ascending e
       sortorder*
                           descending.

         group*            Mostra apenas os contatos do grupo indicado.

 Tabela 3. Parâmetros de query disponíveis para pesquisar Contatos e Grupos.




 13
         Se tentarmos    usar um parâmetro de         query não suportado pelo serviço,          receberemos    uma
ServiceForbiddenException , com a mensagem no formato:“Fobidden. This service does not support the <nome do
parâmetro> parameter”.
 14
         Usando a Java Client Library, para configurar o valor de parâmetros específicos de um serviço, é necessário
usar o método Query.addCustomParameter().
                                                                                               12/26


Aplicação exemplo (Organizador Pessoal)
  Agora que vimos as estruturas básicas dos objetos que lidaremos, bem como as diversas opções de
projeção e parâmetros de query que podem ser utilizados para refinar esses objetos, veremos como
realizar as principais operações disponibilizadas pela API de Contatos Google. Para isso
construiremos uma pequena aplicação Swing, um Organizador Pessoal, que inicialmente irá
manipular apenas os contatos e grupos de um usuário. (Iremos incrementar a mesma aplicação nos
próximos artigos, à medida que formos explorando as demais APIs de dados.)
  Primeiramente precisaremos baixar, caso já não o tenhamos feito, a Java Client Library e suas
dependências. Os links para download encontram-se na seção Links.
  Iniciaremos criando os beans, classes de infra-estrutura e esqueleto da nossa classe de negócio, em
seguida acrescentaremos os métodos responsáveis por cada operação a esta classe e, por fim,
criaremos uma pequena interface gráfica para testar as funcionalidades. As Listagens 3 e 4
mostram, respectivamente, os beans Contact e Group. Poderíamos usar diretamente as classes
ContactEntry e GroupEntry, ambas disponíveis na Java Client Library, mas em geral é melhor
termos nossas próprias classes de domínio, pois conseguimos ter uma certa independência da API,
além de que, num projeto real, são poucas as chances dessas classes atenderem aos requisitos.
  Na classe Contact (Listagem 3), primeiramente temos a definição do enum Job contendo uma
pequena lista de ocupações. Temos em seguida a declaração dos atributos da classe incluindo o
atributo job do tipo Job, e o atributo groups, contendo uma lista de objetos do tipo Group,
representando os grupos dos quais o contato faz parte.
  Perceba que o atributo Job não faz parte dos atributos padrão de uma Entrada de Contato. A nossa
intenção com esse atributo é mostrar o uso de atributos estendidos. Como vimos anteriormente, um
atributo não padrão pode ser mapeado usando o elemento extendedProperty. A Java Client Library
possui uma classe ExtendedProperty para este fim. No método setPhoto() copiamos os bytes da foto
do contato para o atributo da classe. Por fim sobrescrevemos o método toString() para retornar o
nome do contato; isso será útil para montarmos a interface gráfica mais adiante.
  A classe Group é ainda mais simples (ver Listagem 4). Iniciamos com as declarações dos atributos
do grupo. O atributo description também não é um elemento padrão de uma Entrada de Grupo da
API e deve ser mapeado como uma ExtendedProperty. Depois sobrescrevemos o método toString(),
assim como no caso anterior e, por fim, sobrescrevemos também o método equals(), para que
possamos identificar um grupo pelo seu ID.

Listagem 3. Classe de bean Contact. Representa um Contato na nossa aplicação.
// declaração de pacote e imports omitidos.
public class Contact {
  public enum Job {
    OTHER, STUDENT, TRAINEE, DESIGNER, ARCHITECT, ENGINEER, DOCTOR
  }

  private    String id;
  private    String name;
  private    String email;
  private    String phoneNumber;
  private    String mobileNumber;
  private    Job job = Job.OTHER;
  private    byte[] photo;
  private    List<Group> groups = new ArrayList<Group>();

  // gets e sets omitidos...

  public void setPhoto(byte[] photo) {
    if (photo != null) {
      this.photo = new byte[photo.length];
      System.arraycopy(photo, 0, this.photo, 0, photo.length);
    } else {
      this.photo = null;
    }
  }

  public String toString() {
                                                                                                            13/26


         return name;
     }
}

Listagem 4. Classe de bean Group. Representa um Grupo de Contatos na nossa aplicação.
// declaração de pacote e imports omitidos.
public class Group {

     String id;
     String title;
     String description;

     public String toString() { return title; }

     public boolean equals(Object obj) {
       return (obj instanceof Group) && (id != null) && (id.equals(((Group) obj).getId()));
     }

}


 Com nossas classes de domínio criadas, vamos criar as classes que fornecerão a infra-estrutura da
nossa aplicação e a classe de negócio, responsável por interagir com a API de Contatos Google
através dos recursos da Java Client Library.
 A Listagem 5 mostra o código da classe CredentialsManager, responsável por armazenar as
credenciais do usuário. Essa classe é um singleton simples e contém apenas dois atributos, email e
password, com seus métodos get. O método setCredentials() é utilizado para configurar as
credenciais do usuário. A Listagem 6, por sua vez, mostra a interface Constants que contém as
constantes que utilizaremos na aplicação.
 A Listagem 7 mostra a definição da classe abstrata AbstractServiceManager, que irá conter o
comportamento comum a todas as classes responsáveis por gerenciar algum tipo de serviço.
Primeiro temos a declaração do atributo service, do tipo GoogleService. Essa é a classe base das
classes de serviço específicas, como ContactsService, BloggerService, CalendarService, etc. A
seguir temos o atributo authenicated, que servirá para indicar se o usuário já está autenticado num
determinado serviço ou não. O método authenticate() é responsável por autenticar o usuário no
serviço em questão, se este ainda não estiver autenticado. Note que estamos usando a autenticação
do tipo ClientLogin15, que é indicado para aplicações desktop. No construtor, apenas efetuamos
uma chamada ao método reset(), que por sua vez, “seta” para false o atributo authenticated e cria o
objeto do serviço através do método abstrato createService(). Este método deve ser implementado
pelas subclasses responsáveis por cada serviço específico.

Listagem 5. Classe CredentialsManager. Responsável por armazenar as credenciais do usuário.
// declaração de pacote e imports omitidos.
public class CredentialsManager {

     public static CredentialsManager getInstance() { return INSTANCE; }
     public String getEmail() { return email; }
     public String getPassword() { return password; }

     public void setCredentials(String email, String password) {
       this.email = email;
       this.password = password;
     }

     private static final CredentialsManager INSTANCE = new CredentialsManager();
     private String email;
     private String password;

     private CredentialsManager() {}
}



    15
        Atualmente os tipos de autenticação disponíveis são: ClientLogin, AuthSub e OAuth. Para mais detalhes sobre
cada um dos tipos de autenticação, veja o artigo anterior.
                                                                                            14/26


Listagem 6. Interface Constants. Define as constantes globais utilizadas na aplicação.
// declaração de pacote e imports omitidos.
public interface Constants {
  String APPLICATION_NAME = "JavaMagazine-GData-PersonalOrganizer";
  String EXTENDED_PROPERTY_JOB = "Job";
  String EXTENDED_PROPERTY_DESCRIPTION = "Description";
  String CUSTOM_PARAMETER_GROUP = "group";
}

Listagem 7. Classe AbstractServiceManager. Base para os demais gerenciadores de serviços.
// declaração de pacote e imports omitidos.
public abstract class AbstractServiceManager {

    private GoogleService service;
    private boolean authenticated;

    protected void authenticate() {
      if (!authenticated) {
        CredentialsManager cm = CredentialsManager.getInstance();
        try {
          service.setUserCredentials(cm.getEmail(), cm.getPassword());
          authenticated = true;
        } catch (AuthenticationException e) { e.printStackTrace(); }
      }
    }

    public AbstractServiceManager () { reset(); }

    public void reset() {
      authenticated = false;
      service = createService();
    }

    protected abstract GoogleService createService();
}


  Com nossa infra-estrutura de classes criadas, vamos criar a estrutura inicial da classe
ContactsServiceManager, que herdará de AbstractServiceManager e será responsável pela interação
entre nossa aplicação e o serviço de contatos Google através da Google Contacts Data API. O
código dessa classe pode ser visto na Listagem 8. Primeiramente temos o atributo estático
INSTANCE que guarda a única instância dessa classe (padrão singleton). Logo em seguida temos o
atributo service do tipo ContactsService, que é uma subclasse de GoogleService especializada para
trabalhar com Entradas de Contatos e Entradas de Grupos. Temos a implementação do método
createService(), definido como abstrato na classe AbstractServiceManager. Nossa implementação
simplesmente cria um novo objeto ContactsService, armazena uma referência para esse objeto no
atributo service e o retorna.
  O método parseContact() é responsável por converter um ContactEntry no nosso bean Contact.
Primeiro criamos uma nova instância de Contact e configuramos seus ID e Name. Perceba que o
método getTitle() da classe BaseEntry (base de ContactEntry e demais tipos de entrada específicos)
retorna um TextConstruct, por isso precisamos chamar o método getPlainText() para recuperar o
valor String do nome do contato. Em seguida, iteramos sobre os e-mails e telefones presentes no
objeto entry e através do seu atributo rel descobrimos o tipo; estamos recuperando apenas o e-mail
cujo tipo de relação é igual a Email.Rel.OTHER. Usamos os tipos de relação PhoneNumber.Rel.HOME
e PhoneNumber.Rel.MOBILE para identificar o telefone convencional e o celular do contato. Para
cada GroupMembershipInfo presente na entrada, criamos um novo Group e o adicionamos ao
contato. Por fim procuramos o objeto ExtendedProperty cujo nome é igual ao definido na constante
Constants.EXTENDED_PROPERTY_JOB e convertemos seu valor para uma das opções do enum Job.
  O método buildContactEntry() faz o trabalho inverso, ele pega os dados de um objeto Contact e
preenche um ContactEntry com esses dados. A parte menos trivial é a configuração do e-mail,
telefone e celular na entrada. Primeiro verificamos se a entrada já possui algum desses atributos
com o tipo de relação apropriado. Caso a entrada já os possua (um e-mail cujo tipo de relação é
                                                                                                                                  15/26


OTHER, por exemplo), o valor será sobrescrito. Caso contrário, um novo objeto do tipo apropriado é
criado, configurado e adicionado à entrada.
 Os métodos parseGroup() e buildGroupEntry() são semelhantes a parseContact() e
buildContactEntry(), respectivamente, mas lidam com grupos ao invés de contatos. Por último temos
o método getInstance() usado para recuperar a instancia única de ContactsServiceManager.
 Criamos a estrutura básica da nossa classe gerenciadora de contatos e grupos, mas ainda não vimos
como interagir com o servidor. A partir de agora veremos como realizar cada uma das principais
operações providas pela API de Contatos Google.

Listagem 8. Estrutura inicial da classe ContactsServiceManager, responsável por realizar a interação com o serviço de contatos.
// declaração de pacote e imports omitidos.
public class ContactsServiceManager extends AbstractServiceManager {

  private static final ContactsServiceManager INSTANCE = new ContactsServiceManager();
  private ContactsService service;

  protected GoogleService createService() {
    service = new ContactsService(Constants.APPLICATION_NAME);
    return service;
  }

  private Contact parseContact(ContactEntry entry) {
    Contact contact = new Contact();
    contact.setId(entry.getId());
    contact.setName(entry.getTitle().getPlainText());

      for (Email email : entry.getEmailAddresses()) {
        if (email.getRel().equals(Email.Rel.OTHER)) {
          contact.setEmail(email.getAddress());
          break;
        }
      }
      for (PhoneNumber phoneNumber : entry.getPhoneNumbers()) {
        if (phoneNumber.getRel().equals(PhoneNumber.Rel.HOME)) {
          contact.setPhoneNumber(phoneNumber.getPhoneNumber());
        }
        if (phoneNumber.getRel().equals(PhoneNumber.Rel.MOBILE)) {
          contact.setMobileNumber(phoneNumber.getPhoneNumber());
        }
      }
      for (GroupMembershipInfo groupMembershipInfo : entry.getGroupMembershipInfos()) {
        Group group = new Group();
        group.setId(groupMembershipInfo.getHref());
        contact.getGroups().add(group);
      }
      for (ExtendedProperty property : entry.getExtendedProperties()) {
        if (property.getName().equals(Constants.EXTENDED_PROPERTY_JOB)) {
          contact.setJob(Enum.valueOf(Contact.Job.class, property.getValue()));
          break;
        }
      }
      return contact;
  }

  private void buildContactEntry(ContactEntry entry, Contact contact) {

      entry.setTitle(new PlainTextConstruct(contact.getName()));

      boolean existingEmail = false;
      for (Email email : entry.getEmailAddresses()) {
        if (email.getRel().equals(Email.Rel.OTHER)) {
          existingEmail = true;
          if ((contact.getEmail() != null) && (!contact.getEmail().equals(""))) {
            email.setAddress(contact.getEmail());
          } else { entry.getEmailAddresses().remove(email); }
          break;
        }
      }
      if ((!existingEmail) && (contact.getEmail() != null) && (!contact.getEmail().equals(""))) {
        Email email = new Email();
        email.setRel(Email.Rel.OTHER);
        email.setAddress(contact.getEmail());
                                                                                    16/26


      entry.addEmailAddress(email);
    }
    boolean existingPhone = false;
    for (PhoneNumber phoneNumber : entry.getPhoneNumbers()) {
      if (phoneNumber.getRel().equals(PhoneNumber.Rel.HOME)) {
        existingPhone = true;
        if ((contact.getPhoneNumber() != null)
            && (!contact.getPhoneNumber().equals(""))) {
          phoneNumber.setPhoneNumber(contact.getPhoneNumber());
        } else { entry.getPhoneNumbers().remove(phoneNumber); }
        break;
      }
    }
    if ((!existingPhone) && (contact.getPhoneNumber() != null)
        && (!contact.getPhoneNumber().equals(""))) {
      PhoneNumber phoneNumber = new PhoneNumber();
      phoneNumber.setRel(PhoneNumber.Rel.HOME);
      phoneNumber.setPhoneNumber(contact.getPhoneNumber());
      entry.addPhoneNumber(phoneNumber);
    }
    boolean existingMobile = false;
    for (PhoneNumber phoneNumber : entry.getPhoneNumbers()) {
      if (phoneNumber.getRel().equals(PhoneNumber.Rel.MOBILE)) {
        existingMobile = true;
        if ((contact.getMobileNumber() != null)
            && (!contact.getMobileNumber().equals(""))) {
          phoneNumber.setPhoneNumber(contact.getMobileNumber());
        } else { entry.getPhoneNumbers().remove(phoneNumber); }
        break;
      }
    }
    if ((!existingMobile) && (contact.getMobileNumber() != null)
        && (!contact.getMobileNumber().equals(""))) {
      PhoneNumber mobileNumber = new PhoneNumber();
      mobileNumber.setRel(PhoneNumber.Rel.MOBILE);
      mobileNumber.setPhoneNumber(contact.getMobileNumber());
      entry.addPhoneNumber(mobileNumber);
    }
    for (Group group : contact.getGroups()) {
      GroupMembershipInfo groupMembershipInfo = new GroupMembershipInfo();
      groupMembershipInfo.setHref(group.getId());
      entry.addGroupMembershipInfo(groupMembershipInfo);
    }
    if (contact.getJob() != null) {
      ExtendedProperty job = new ExtendedProperty();
      job.setName(Constants.EXTENDED_PROPERTY_JOB);
      job.setValue(contact.getJob().name());
      entry.addExtendedProperty(job);
    }
}

private Group parseGroup(ContactGroupEntry entry) {
  Group group = new Group();
  group.setId(entry.getId());
  group.setTitle(entry.getTitle().getPlainText());

    for (ExtendedProperty property : entry.getExtendedProperties()) {
      if (property.getName().equals(Constants.EXTENDED_PROPERTY_DESCRIPTION)) {
        group.setDescription(property.getValue());
      }
    }

    return group;
}

private void buildGroupEntry(ContactGroupEntry entry, Group group) {
  if ((group.getTitle() != null) && (!group.getTitle().equals(""))) {
    entry.setTitle(new PlainTextConstruct(group.getTitle()));
  }
  if ((group.getDescription() != null) && (!group.getDescription().equals(""))) {
    ExtendedProperty description = new ExtendedProperty();
    description.setName(Constants.EXTENDED_PROPERTY_DESCRIPTION);
    description.setValue(group.getDescription());
    entry.addExtendedProperty(description);
  }
}

public static ContactsServiceManager getInstance() {
                                                                                                        17/26


        return INSTANCE;
    }

}

Inserindo um novo contato com foto
 No artigo anterior, vimos que para inserir uma nova entrada basta enviar um HTTP POST para o
endereço do feed, colocando a representação XML da nova entrada no corpo da requisição. Sendo
assim, para inserir um novo contato teríamos uma requisição parecida com a seguinte:

POST /m8/feeds/contacts/usuarioteste%40gmail.com/full

<?xml version="1.0"?>
<atom:entry xmlns="http://www.w3.org/2005/Atom">
  <atom:category scheme='http://schemas.google.com/g/2005#kind'
term='http://schemas.google.com/contact/2008#contact'/>
  ...
</atom:entry>

 Não é possível configurar a foto do contato no momento da criação deste. Para adicionar ou editar
a foto de um contato é preciso uma requisição adicional. Para tal, basta enviar um HTTP PUT, com
os bytes da foto no corpo da requisição, para o link de edição de foto, criado automaticamente pelo
servidor quando um novo contato é criado.
 Vejamos então como fazer isso usando a Java Client Library. A Listagem 9 mostra o método
createContact() que adicionaremos à classe ContactsServiceManager. Iniciamos chamando o método
authenticate(), visto anteriormente, para realizar a autenticação do usuário no serviço de contatos.
Criamos um novo ContactEntry e o preenchemos com os dados do contato recebido através do
método buildContactEntry(). Em seguida criamos a URL do feed de contatos do usuário e chamamos
o método insert() do objeto service. Este método trata todas as questões do protocolo relacionadas à
inserção de uma Entrada de Contato. Note que o método insert() retorna um objeto ContactEntry,
que já contém todos os dados acrescentados pelo servidor no momento da criação. Então se o
contato possuir uma foto, enviamos seus bytes para o servidor. Para isso recuperamos o link de
edição de foto através do método getContactEditPhotoLink() de ContactEntry, criando uma URL com
este link; criamos um objeto GDataRequest chamando o método createRequest() no objeto service,
informando o tipo de requisição (UPDATE no caso), a URL e o tipo de conteúdo (em nosso caso,
“image/jpeg”); escrevemos os bytes da foto no OutputStream da requisição e, finalmente,
executamos a requisição chamando o método execute(). Por último, a entrada atualizada é
convertida num objeto Contact através do método parseContact() e esse objeto é retornado.

Listagem 9. Método createContact(), responsável por criar um contato no servidor, incluindo sua foto.
// declaração de pacote e imports omitidos.
public class ContactsServiceManager extends AbstractServiceManager {
  ...

    public Contact createContact(Contact contact) {
      Contact result = contact;

        try {
          authenticate();
          ContactEntry entry = new ContactEntry();
          buildContactEntry(entry, contact);
          URL feedUrl = new URL("http://www.google.com/m8/feeds/contacts/"
              + CredentialsManager.getInstance().getEmail() + "/full");
          entry = service.insert(feedUrl, entry);

         if (contact.getPhoto() != null) {
           Link photoEditLink = entry.getContactEditPhotoLink();
           URL photoEditUrl = new URL(photoEditLink.getHref());
           GDataRequest request = service.createRequest(
                                                                                                           18/26


                GDataRequest.RequestType.UPDATE, photoEditUrl, new ContentType("image/jpeg"));
            OutputStream out = request.getRequestStream();
            out.write(contact.getPhoto());
            request.execute();
           }
           result = parseContact(entry);
         } catch (Exception e) { e.printStackTrace(); }
         return result;
     }
}



Atualizando um contato e sua foto
 Para atualizar uma Entrada de Contato basta enviar uma requisição HTTP PUT para o endereço de
edição da entrada, inserindo a representação XML da entrada atualizada no corpo. Uma requisição
de atualização se parece com o seguinte:

PUT /m8/feeds/contacts/usuarioteste%40gmail.com/full/123214/54bvd77

<?xml version="1.0"?>
<atom:entry xmlns="http://www.w3.org/2005/Atom">
  <atom:category scheme='http://schemas.google.com/g/2005#kind'
term='http://schemas.google.com/contact/2008#contact'/>
  ...
</atom:entry>

  Agora vejamos como fazê-lo usando a Java Client Library. Acrescentemos o método editContact()
(Listagem 10) à classe ContactsServiceManager. Esse método é semelhante a createContact(), já
visto, com algumas diferenças que discutiremos agora. Primeiramente recuperamos a entrada do
servidor usando o método getEntry() de service. Note que a URL utilizada para recuperar um
contato é seu próprio ID. De posse da entrada, atualizamos seus dados, utilizando o contato
recebido como parâmetro, através do método buildContactEntry(). Depois de realizadas as devidas
atualizações, chamamos o método update() do objeto service, a fim de atualizar os dados do contato
no servidor. Perceba que criamos a URL usando o link de edição da entrada, porém substituindo a
projeção base16 por full. Isto é necessário, pois estamos utilizando atributos estendidos. Caso não
fizéssemos essa substituição, o valor do atributo job nunca seria atualizado. O código para
atualização da foto é quase idêntico ao de createContact(), com a diferença de que se o contato
recebido tiver seu atributo photo igual a null, entenderemos que o usuário deseja remover a foto
para esse contato. Nesse caso, chamamos o método delete() do objeto service. No final,
preenchemos o contato com os dados atualizados e o retornamos.

Listagem 10. Método editContact(), responsável por editar um contato e sua foto no servidor.
// declaração de pacote e imports omitidos.
public class ContactsServiceManager extends AbstractServiceManager {
  ...
  public Contact editContact(Contact contact) {
    Contact result = contact;

         try {
           authenticate();
           URL entryUrl = new URL(contact.getId());
           ContactEntry entry = service.getEntry(entryUrl, ContactEntry.class);
           buildContactEntry(entry, contact);

          URL editUrl = new URL(entry.getEditLink().getHref().replace("/base/", "/full/"));
          entry = service.update(editUrl, entry);


    16
         O valor de projeção base é equivalente ao thin. Quando recuperamos os contatos do servidor, os IDs desses
contatos sempre usam a projeção base. Atualmente, não existe nenhuma referência a esse valor de projeção na
documentação oficial da API.
                                                                                                  19/26


           Link photoEditLink = entry.getContactEditPhotoLink();
           URL photoEditUrl = new URL(photoEditLink.getHref());
           if (contact.getPhoto() != null) {
             GDataRequest request = service.createRequest(
                 GDataRequest.RequestType.UPDATE, photoEditUrl, new ContentType("image/jpeg"));
             OutputStream out = request.getRequestStream();
             out.write(contact.getPhoto());
             request.execute();
           } else {
             if (entry.getContactPhotoLink() != null) { service.delete(photoEditUrl); }
           }
           result = parseContact(entry);
         } catch (Exception e) { e.printStackTrace(); }
         return result;
     }
}



Removendo um contato
 Para remover um contato, tudo o que temos de fazer é enviar uma requisição HTTP DELETE para o
endereço de edição da entrada, como no exemplo a seguir:

DELETE /m8/feeds/contacts/usuarioteste%40gmail.com/full/123214/54bvd77

 Vamos ver como seria um código Java para realizar essa tarefa. A Listagem 11 mostra o método
deleteContact(), acrescentado à classe ContactsServiceManager. Nele, iniciamos autenticando o
usuário no serviço, recuperamos a entrada correspondente ao objeto Contact recebido como
parâmetro, criamos uma URL com o endereço de edição da entrada e, por fim, chamamos o método
delete() do objeto service.

Listagem 11. Método deleteContact(), responsável por remover um contato no servidor.
// declaração de pacote e imports omitidos.
public class ContactsServiceManager extends AbstractServiceManager {
  ...
  public void deleteContact(Contact contact) {
    try {
      authenticate();
      URL entryUrl = new URL(contact.getId());
      ContactEntry entry = service.getEntry(entryUrl, ContactEntry.class);
      URL editUrl = new URL(entry.getEditLink().getHref());
      service.delete(editUrl);
    } catch (Exception e) { e.printStackTrace(); }
  }
}



Listando os contatos do usuário
 Para recuperar a lista de contatos de um usuário basta enviar um HTTP GET para o endereço do
feed de contatos desse usuário. Opcionalmente, podemos acrescentar parâmetros 17 à URL do feed
para interferir no que será retornado. O exemplo a seguir mostra como seria a URL para recuperar
os contatos do usuário “usuarioteste@gmail.com” pertencentes ao grupo “meugrupo”:

GET /m8/feeds/contacts/usuarioteste%40gmail.com/full/?group=meugrupo

 Vejamos então como fazer isso no Java. A Listagem 12 mostra o método listContacts(), também
adicionado à classe ContactsServiceManager. Primeiro criamos a lista de contatos que será
retornada. Na seqüência, montamos a URL do feed de contatos do usuário autenticado. Criamos um
objeto Query que conterá os parâmetros adicionais. Configuramos a quantidade máxima de
resultados para 10000, para que todos os contatos do usuário possam ser retornados. Se algum
    17
            Já vimos anteriormente as opções de parâmetros disponíveis para a API de Contatos.
                                                                                                         20/26


grupo foi informado, acrescentamos um filtro por grupo. Após isso, recuperamos um objeto
ContactFeed, contendo todas as entradas de contatos, através do método query()18 do objeto service.
Uma vez com o ContactFeed em mãos, iteramos pelas entradas desse feed, recuperadas através do
método getEntries(), convertemos essas entradas em objetos Contact e adicionamos esses objetos à
lista que criamos. Por fim, retornamos a lista de contatos. A segunda versão é apenas um método de
conveniência para recuperar os contatos de qualquer grupo.

Listagem 12. Método listContacts(), responsável por listar os contatos do usuário.
// declaração de pacote e imports omitidos.
public class ContactsServiceManager extends AbstractServiceManager {
  ...
  public List<Contact> listContacts(String groupId) {
    List<Contact> list = new ArrayList<Contact>();

         try {
           authenticate();
           URL feedUrl = new URL("http://www.google.com/m8/feeds/contacts/"
               + CredentialsManager.getInstance().getEmail() + "/full");

           Query query = new Query(feedUrl);
           query.setMaxResults(10000);
           if ((groupId != null) && (!groupId.equals(""))) {
             query.addCustomParameter(new Query.CustomParameter(
                 Constants.CUSTOM_PARAMETER_GROUP, groupId));
           }
           ContactFeed resultFeed = service.query(query, ContactFeed.class);
           for (ContactEntry entry : resultFeed.getEntries()) {
             list.add(parseContact(entry));
           }
         } catch (Exception e) { e.printStackTrace(); }

         return list;
     }

     public List<Contact> listContacts() { return listContacts(null); }
}



Recuperando os dados de um contato incluindo sua foto
 Para recuperar as informações que dizem respeito a um determinado contato, basta enviar uma
requisição HTTP GET para a URL que representa o ID do contato. Veja um exemplo:

GET /m8/feeds/contacts/usuarioteste%40gmail.com/full/as7a6s8ddw

 A foto do contato não faz parte da Entrada de Contato recuperada pelo exemplo anterior. Para
recuperar os bytes que representam a foto de um contato é necessária uma requisição específica
para o link da foto do contato. Veja um exemplo:

GET http://google.com/m8/feeds/photos/media/usuarioteste%40gmail.com/c9012de

 A Listagem 13 mostra o método getContact() adicionado à classe ContactsServiceManager. Nele,
primeiramente autenticamos o usuário, como nos demais métodos, em seguida recuperamos a
Entrada de Contato correspondente ao ID recebido. Construímos um Contact a partir da entrada
recuperada, verificamos se a entrada possui um link de foto e se esta possuir criamos um
InputStream, usando o método getStreamFormLink() do objeto service para recuperar os bytes da
foto. Por fim, configuramos a foto no objeto contact criado e retornamos esse objeto.

Listagem 13. Método getContact(), responsável por recuperar os dados de um contato incluindo sua foto.

    18
            Se não tivéssemos nenhum parâmetro adicional na requisição, poderíamos simplesmente chamar o método
getFeed() no objeto service, eliminando a necessidade de um objeto Query.
                                                                                          21/26


// declaração de pacote e imports omitidos.
public class ContactsServiceManager extends AbstractServiceManager {
  ...
  public Contact getContact(String contactId) {
    Contact contact = null;

        try {
          authenticate();
          URL entryUrl = new URL(contactId.replace("/base/", "/full/"));
          ContactEntry entry = service.getEntry(entryUrl, ContactEntry.class);

          if (entry != null) {
            contact = parseContact(entry);
            Link photoLink = entry.getContactPhotoLink();
            if (photoLink != null) {
              InputStream in = service.getStreamFromLink(photoLink);
              ByteArrayOutputStream out = new ByteArrayOutputStream();
              byte[] buffer = new byte[2048];
              for (int read = 0; (read = in.read(buffer)) != -1;) {
                out.write(buffer, 0, read);
              }
              contact.setPhoto(out.toByteArray());
            }
          }
        } catch (Exception e) { e.printStackTrace(); }
        return contact;
    }
}



Gerenciando Grupos de Usuários
 Como os métodos para gerenciamento de grupos de contatos são muito parecidos com os de
gerenciamento de Contatos, porém mais simples, não explicaremos detalhadamente cada método
para não tornar o artigo muito extenso. A Listagem 14 mostra a implementação dos métodos
getGroup(), createGroup(), editGroup(), deleteGroup() e listGroups(), todos adicionados à classe
ContactsServiceManager. Note que a lógica é bastante parecida com a dos métodos que gerenciam
contatos, com a diferença que usam os tipos ContactGroupEntry, ContactGroupFeed e Group.

Listagem 14. Métodos para gerenciamento de Grupos de Contatos.
// declaração de pacote e imports omitidos.
public Group getGroup(String groupId) {
    Group group = null;
    try {
      authenticate();
      URL entryUrl = new URL(groupId.replace("/base/", "/full/"));
      ContactGroupEntry entry = service.getEntry(entryUrl, ContactGroupEntry.class);
      if (entry != null) {
        group = parseGroup(entry);
      }
    } catch (Exception e) { e.printStackTrace(); }
    return group;
  }

    public Group createGroup(Group group) {
      Group result = group;
      try {
        authenticate();
        ContactGroupEntry entry = new ContactGroupEntry();
        URL feedUrl = new URL("http://www.google.com/m8/feeds/groups/"
            + CredentialsManager.getInstance().getEmail() + "/full");
        buildGroupEntry(entry, group);
        entry = service.insert(feedUrl, entry);
        result = parseGroup(entry);
      } catch (Exception e) { e.printStackTrace(); }
      return result;
    }

    public Group editGroup(Group group) {
      Group result = group;
      try {
        authenticate();
        URL entryUrl = new URL(group.getId());
                                                                                                22/26


       ContactGroupEntry entry = service.getEntry(entryUrl, ContactGroupEntry.class);
       URL editUrl = new URL(entry.getEditLink().getHref().replace("/base/", "/full/"));
       buildGroupEntry(entry, group);
       entry = service.update(editUrl, entry);
       result = parseGroup(entry);
     } catch (Exception e) { e.printStackTrace(); }
     return result;
 }

 public void deleteGroup(Group group) {
   try {
     authenticate();
     URL entryUrl = new URL(group.getId());
     ContactGroupEntry entry = service.getEntry(entryUrl, ContactGroupEntry.class);
     URL editUrl = new URL(entry.getEditLink().getHref());
     service.delete(editUrl);
   } catch (Exception e) { e.printStackTrace(); }
 }

 public List<Group> listGroups() {
   List<Group> list = new ArrayList<Group>();
   try {
     authenticate();
     URL feedUrl = new URL("http://www.google.com/m8/feeds/groups/"
         + CredentialsManager.getInstance().getEmail() + "/full");
     ContactGroupFeed resultFeed = service.getFeed(feedUrl, ContactGroupFeed.class);
     for (ContactGroupEntry entry : resultFeed.getEntries()) {
       list.add(parseGroup(entry));
     }
   } catch (Exception e) { e.printStackTrace(); }

     return list;
 }


 Com isso concluímos nosso gerenciador de contatos e grupos de contatos usando o serviço
ContactsService. Agora temos a infra-estrutura necessária para realizar as operações básicas
envolvendo contatos e grupos.

Interface Gráfica
  A interface gráfica de nossa aplicação será um conjunto simples de telas feitas em Swing para
exibir as listas de contatos e grupos, bem como visualizar e alterar as informações relativas a esses.
  Como esse não é o foco principal deste artigo, o código referente à interface gráfica da nossa
aplicação não será mostrado aqui; também deixamos de lado vários pontos relevantes numa GUI
profissional, como validação e formatação de dados. Mas o código da interface está disponível para
download no site da Java Magazine, bem como o código dos módulos que vimos até agora. As
Figuras 1 e 2 mostram, respectivamente, a tela principal da nossa aplicação e a tela de visualização
/ edição dos dados de um contato.
                                                                                              23/26




 Figura 1. Tela principal da aplicação.




 Figura 2. Tela de visualização / edição dos dados de um contato.

Conclusões
 Neste artigo aprendemos, de forma prática, o funcionamento da API de Contatos Google. Vimos
como realizar as principais operações que envolvem o gerenciamento dos contatos e grupos de
contatos de um usuário e criamos uma aplicação Swing simples, usando a Java Client Library, para
testar essas funcionalidades. Nossa aplicação, contudo, possui suas próprias classes de domínio, que
é o que acontece na maioria dos casos reais. Usamos também uma camada capaz de abstrair os
serviços e tipos específicos da biblioteca, diminuindo assim a dependência com o serviço Google
especificamente. Também criamos uma infra-estrutura básica para que possamos acrescentar novos
serviços à nossa aplicação.
                                                                                             24/26


 Com isso concluímos o estudo da primeira das APIs de Dados Google. Nos próximos artigos
conheceremos mais alguns dos serviços providos pelas Google Data APIs e os usaremos para
incrementar nossa aplicação. Até a próxima!

 De que se trata o artigo:

 Este artigo mostra, de forma prática, as principais operações oferecidas pela API de Contatos
Google. Aqui são apresentados os elementos envolvidos no que diz respeito ao gerenciamento de
contatos e grupos de contatos de um usuário, bem como as operações realizadas utilizando esses
elementos. Para mostrar essas funcionalidades criamos uma aplicação Java Swing, utilizando a Java
Client Library.

 Para que serve:

 Com o uso das APIs de Dados Google, de um forma geral, torna-se possível integrar nossas
aplicações com vários dos serviços disponibilizados pelo Google. No caso da API de Contatos,
nossa aplicação torna-se capaz de recuperar os contatos e grupos de um usuário, bem como criar,
remover e alterar esses contatos e grupos.

 Em que situação o tema é útil:

 Nos dias de hoje, a integração entre sistemas está tornando-se quase uma obrigação. Existem
várias frentes de trabalho que têm se esforçado no sentido de criar padronizações e meios para
facilitar, cada vez mais, essa integração. O Google, diante desse cenário, oferece diversas
facilidades, dentre as quais estão as APIs de Dados. Com a API de contatos, no nosso caso,
podemos integrar facilmente nossas aplicações com o serviço de contatos e grupos do Google. Isso
nos permite realizar facilmente tarefas como a sincronização dos contatos de uma aplicação desktop
com nossos contatos Google e assim por diante.

  Google Data APIs – Contacts:
  A API de Contatos Google faz parte das diversas APIs de Dados disponíveis. Com ela é possível
recuperar, criar, editar e excluir contatos e grupos de um usuário. Também é possível definir novas
propriedades para os contatos. Essas propriedades são armazenadas no servidor como propriedades
estendidas.
  No artigo anterior, Google Data APIs, vimos que as APIs de Dados trabalham sobre o GData
Protocol, um protocolo do Google criado especialmente para essas APIs, e que os dados são
trocados com o servidor no formato XML. Também foi visto que o Google disponibiliza bibliotecas
cliente em várias linguagens, incluindo Java. Essas bibliotecas fornecem uma camada de abstração
sobre o protocolo GData. Com isso é possível interagir com as APIs de dados usando classes
objetos comuns Java. No caso da biblioteca Java, por exemplo, cada serviço possui uma classe
correspondente, todas derivadas de uma classe de serviço genérica.
  Para representar o serviço de contatos temos a classe ContactsService. Usando essa classe, no
lugar da classe Service genérica, temos a vantagem de trabalhar diretamente com os tipos
específicos do serviço de contatos sem a necessidade de realizar typecasts.
  Para inserir um contato, basta criar um ContactEntry, preenchê-lo adequadamente, incluindo as
propriedades estendidas, e passá-lo como parâmetro para o método ContactsService.insert(),
juntamente com a URL do feed de contatos.
  Para editar um contato existente, o que temos que fazer é recuperar a entrada a ser atualizada,
editar o objeto ContactEntry da forma que desejamos e passá-lo para método
ContactsService.update(). Nesse caso devemos passar a URL com o endereço de edição do contato.
Para recuperar o link de edição de um contato, use o método ContactEntry.getEditLink().
                                                                                            25/26


 Para remover um contato basta chamar o método ContactsService.delete(), passando a URL com o
endereço de edição do contato.
 Para listar todos os contatos, é possível usar o método ContactsService.getFeed() ou
ContactsService.query(). Ambos retornam um ContactsFeed, o qual possui um método getEntries()
que retorna uma lista com todas as entradas recuperadas. O método query() deve ser utilizado
quando desejamos fornecer parâmetros para a consulta.
 Para recuperar um contato específico, podemos usar o método ContactsService.getEntry() passando
a URL que representa tal contato. Esse endereço pode ser recuperado através do método
ContactEntry.getId().
 Para recuperar os bytes da imagem do contato é preciso uma requisição específica. Para isso basta
criar um InputStream e associá-lo ao retorno do método ContactsService.getStreamFromLink(). Esse
método recebe uma URL como parâmetro, que nesse caso seria o endereço da foto do contato,
recuperado através do método ContactEntry.getContactPhotoLink().
 As operações com grupos são bem similares. A diferença básica é que são usados os objetos
ContactGroupEntry e ContactGroupFeed.


Google Data APIs – Conceitos Principais

 Este quadro traz alguns dos conceitos principais relacionados as APIs de Dados do Google, para
ajudar no entendimento do artigo:

  Google Data APIs – Conjunto de APIs responsáveis por permitir a interação entre aplicativos
terceiros e os serviços Google. São disponibilizados por meio de webservices RESTful;
  GData Protocol – Protocolo utilizado pelas APIs de Dados Google. É baseado principalmente no
Atom e AtomPub. Atualmente os dados podem ser transmitidos nos formatos XML ou JSON, sendo
XML o padrão.
  Feed – Representa uma fonte de dados freqüentemente atualizada que geralmente é utilizado para
alimentar algum tipo de cliente. Também utilizamos o termo feed para nos referir a URL onde o
feed em si pode ser encontrado;
  Entrada (Entry) – Representa uma entrada de dados num determidado feed. Uma tag Entry.
Corresponde a um registro numa tabela de um banco de dados.
  Java Client Library – Biblioteca Java disponibilizada pelo Google para facilitar o uso das APIs
de dados. Também existem bibliotecas para outras linguagens como PHP, Javascript, Objective-C,
.NET, etc.


Links
http://code.google.com/apis/gdata/
Site oficial das Google Data APIs.
http://code.google.com/apis/contacts/
Site oficial da Google Contact Data API.
http://code.google.com/apis/contacts/developers_guide_java.html
Guia da desenvolvimento da API de Contatos para programadores Java.
http://code.google.com/p/gdata-java-client/downloads/list
Site para download da Java Client Library.
http://java.sun.com/products/javamail/downloads/index.html
Site para download da JavaMail API (arquivo mail.jar).
http://java.sun.com/products/javabeans/jaf/downloads/index.html
Site para download do JavaBeansActivationFramework. (arquivo activation.jar)
http://tomcat.apache.org/download-60.cgi
Site para download do Tomcat (arquivo servlet-api.jar).
                                                                                                                26/26


Paulo César M. N. A. Coutinho (pcmnac@gmail.com) é graduado em Tecnologia em Sistemas de Informação pelo Centro Federal
de Educação Tecnológica de Pernambuco (CEFETPE). Atualmente, trabalha como Engenheiro de Sistemas no Centro de Estudos
e Sistemas Avançados do Recife (C.E.S.A.R) com desenvolvimento desktop em C/C++ e Flex, e web com Java e PHP. Trabalhou
dois anos com desenvolvimento móvel com C/C++. Também tem trabalhado com Java e AspectJ no desenvolvimento de
componentes open-source em projetos pessoais. Possui as certificações SCJP 5 e SCWCD 1.4.

				
DOCUMENT INFO