Conteúdo


Mastering Grails

Criando um plug-in customizado

Compartilhando funcionalidades entre os aplicativos do Grails

Comments

Conteúdos da série:

Esse conteúdo é a parte # de # na série: Mastering Grails

Fique ligado em conteúdos adicionais dessa série.

Esse conteúdo é parte da série:Mastering Grails

Fique ligado em conteúdos adicionais dessa série.

Diversos artigos da série Mastering Grails enfocaram a reutilização inteligente de códigos. Se você já passou pela experiência de copiar e colar o mesmo fragmento de código GroovyServer Pages (GSP) em diversos locais, saiba que é possível criar um modelo parcial ou um TagLib customizado. Se você encontrar um ou dois métodos comuns a múltiplos controladores ou classes de domínio, é possível criar uma classe-pai abstrata para a qual estender ou inserir os métodos diretamente, usando o ExpandoMetaClass. Caso possua alguma funcionalidade compartilhada de aplicativo, é possível refatorá-la para um serviço ou codec customizado.

Mas tudo isso se encontra apenas no micronível. E se você tivesse alguma funcionalidade compartilhada no macronível: algo que requer o esforço combinado e coordenado de controladores e classes de domínio, serviços e codecs e todos os outros componentes de um típico aplicativo Grails? Acabo de descrever um plug-in.

No artigo "Mastering Grails: Understanding plug-ins", você explorou um plug-in já existente: o Searchable. Há mais de 250 plug-ins do Grails disponíveis no portal Plug-ins do Grails (consulte Recursos). Esse número continua crescendo porque estender seu aplicativo Grails existente por meio de plug-ins é uma ideia gerada na essência do Grails. Neste artigo, você aprenderá como criar seu próprio plug-in customizado. O código fonte do plug-in de exemplo está disponível para download.

Apresentando o plug-in ShortenUrl

Na era do Twitter.com e das mensagens de texto de celulares, URLs muito longas que não cabem no limite de 140 caracteres das mensagens podem ser inconvenientes. Felizmente, diversos serviços da Web para a abreviação de URLs estão aí, praticamente implorando para serem integrados ao Grails como plug-in customizado.

Para criar um plug-in customizado, é necessário alterar ligeiramente sua rotina do Grails. Em vez de digitar grails create-app como você normalmente faria, digite grails create-plugin, como mostrado na Listagem 1 (digite esse comando em um diretório novo e vazio, não em um diretório existente do Grails. Ao final deste artigo, você irá aprender como integrar esse novo plug-in a um aplicativo Grails existente).

Listagem 1. Criando um plug-in customizado
$ grails create-plugin shortenurl

A estrutura do diretório resultante é idêntica a um típico aplicativo Grails. No entanto, no diretório-raiz você irá ver um arquivo novo que identifica esse projeto como um plug-in: ShortenurlGrailsPlugin.groovy. A Listagem 2 mostra um fragmento:

Listagem 2. O arquivo de configuração de plug-in
class ShortenurlGrailsPlugin {
    // the plugin version
    def version = "0.1"
    // the version or versions of Grails the plugin is designed for
    def grailsVersion = "1.1.1 > *"
    // the other plugins this plugin depends on
    def dependsOn = [:]
    // resources that are excluded from plugin packaging
    def pluginExcludes = [
            "grails-app/views/error.gsp"
    ]

    // TODO Fill in these fields
    def author = "Your name"
    def authorEmail = ""
    def title = "Plugin summary/headline"
    def description = '''\\
Brief description of the plugin.
'''

    //snip
}

Esse arquivo contém os metadados do seu plug-in: o número da versão, a versão do Grails do qual seu plug-in depende, outros plug-ins dos quais o seu plug-in seja dependente e assim por diante. (Para obter detalhes completos sobre o arquivo de configuração, consulte a documentação on-line em Recursos).

Se você quiser disponibilizar publicamente esse plug-in para que outros desenvolvedores possam baixá-lo no portal de Plug-ins, preencha as informações sobre o autor e forneça ao plug-in uma descrição interessante. O conteúdo do arquivo é lido e automaticamente exibido no Web site do Grails todas as vezes que você marcar seu plug-in no repositório público do Subversion. (Para obter mais informações sobre como publicar seu plug-in, consulte Recursos). Neste artigo, você manterá esse plug-in como privado, por isso preencher as informações do autor não é tão importante.

Mesmo que o plug-in ShortenUrl não exija nenhuma alteração no ShortenurlGrailsPlugin.groovy, isso não significa que o trabalho já esteja concluído. Agora que a estrutura do diretório está expandida, a próxima etapa é escrever a implementação.

Crie a classe TinyUrl

O TinyUrl.com é um serviço popular de abreviação de URL. Quando alguém envia uma URL longa para ser abreviada, a URL é salva em segundo plano como a URL abreviada oficial para todas as solicitações subsequentes. Por exemplo, visite o site, digite http://www.grails.org/The+Plug-in+Developers+Guide e clique no botão Make TinyURL!. A URL abreviada resultante — http://tinyurl.com/73495c — tem metade do comprimento da original, como é possível ver na Figura 1:

Figura 1. TinyURL.com abreviando uma URL
TinyURL.com abreviando uma URL
TinyURL.com abreviando uma URL

Agora que você sabe como o TinyURL.com funciona, é hora de se concentrar na integração do serviço subjacente do site ao plug-in ShortenUrl. Digite o seguinte no seu navegador da Web:

http://tinyurl.com/api-create.php?url=http://www.grails.org/The+Plug-in+Developers+Guide

A interface simples desse serviço da Web retorna apenas a URL abreviada — não o HTML — da página especificada.

A próxima etapa é sintetizar sua nova descoberta em uma classe Groovy. Essa classe é um Plan Old Groovy Object (POGO) no sentido mais estrito da palavra — não é um serviço, um controlador ou qualquer outro componente do Grails com alguma finalidade especial. Por isso, o melhor local para colocá-la é em src/groovy. Crie um diretório org/grails/shortenurl em src/groovy. Em seguida, crie o TinyUrl.groovy e adicione o código presente na Listagem 3:

Listagem 3. A classe do utilitário TinyUrl
package org.grails.shortenurl

class TinyUrl{
  static String shorten(String longUrl){
    def addr = "http://tinyurl.com/api-create.php?url=${longUrl}"
    return addr.toURL().text
  }
}

Testando a classe TinyUrl

Antes que o código seja usado na produção deveria haver um teste correspondente, você não acha? Como você está fazendo uma chamada da Web ao vivo, o teste deve ser um teste de integração. Crie a mesma estrutura do diretório org/grails/shortenurl que você criou anteriormente no teste/integração. Crie oTinyUrlTests.groovy e adicione o código presente na Listagem 4 (Sim, adoro a ironia de, neste caso simples, a suposta URL abreviada ser mais longa que a URL original que está sendo codificada).

Listagem 4. Testando a classe TinyUrl
package org.grails.shortenurl

class TinyUrlTests extends GroovyTestCase{
  def transactional = false

  void testShorten(){    
    def shortUrl = TinyUrl.shorten("http://grails.org")
    assertEquals "http://tinyurl.com/3xfpkv", shortUrl
  }
}

Observe a linha def transactional = false no teste de integração. Se você deixar essa linha de fora, ganhará a desagradável mensagem de erro mostrada na Listagem 5:

Listagem 5. O que acontece se seu teste não definir def transactional = false
Error running integration tests: java.lang.RuntimeException: 
There is no test TransactionManager defined 
and integration test ${test.name} does not set transactional = false

O Grails tenta agrupar cada teste em uma transação de banco de dados. Em uma aplicação Grails comum, isso não é um problema. No entanto, como você está trabalhando com um plug-in e não com um aplicativo completo do Grails, não é possível presumir que o banco de dados esteja disponível. É possível instalar o plug-in Hibernate ou fazer o que a mensagem solicita e definir def transactional = false no seu teste de integração.

Digite grails test-app e verifique se o teste é aprovado.

Irei implementar mais um serviço de abreviação de URL aqui, apenas para dar ao usuário desse plug-in uma opção de serviço.

Criando a classe IsGd

O serviço Is.Gd (pronuncia-se is good) oferece um nome de domínio mais curto e uma URL codificada mais curta do que o fornecido pelo TinyUrl.com. Visite http://is.gd para experimentar o serviço na interface da Web.

Em mais um caso de ironia, aproveitarei esta oportunidade para mostrar uma implementação mais longa do método de duas linhas que usei no TinyUrl.groovy (consulte a Listagem 3). Esta implementação fornece um pouco mais de informações para a reação no caso de falha na chamada do serviço. Crie o IsGd.groovy, mostrado na Listagem 6, em in src/groovy/org/grails/shortenurl:

Listagem 6. A classe do utilitário IsGd
package org.grails.shortenurl

class IsGd{
  static String shorten(String longUrl){
    def addr = "http://is.gd/api.php?longurl=${longUrl}"
    def url = addr.toURL()
    def urlConnection = url.openConnection()
    if(urlConnection.responseCode == 200){
      return urlConnection.content.text
    }else{
      return "An error occurred: ${addr}\n" + 
      "${urlConnection.responseCode} : ${urlConnection.responseMessage}"
    }
  }
}

Como você está vendo, é possível verificar explicitamente que o código de resposta é 200 — o código de resposta HTTP para OK. (Consulte Recursos para obter mais informações sobre códigos de resposta HTTP). Para fins de simplicidade, apenas retorno a mensagem de erro se a chamada falhar. No entanto, com essa estrutura ampliada, é possível tornar o método mais robusto recuperando a chamada mais algumas vezes ou executando failover para outro serviço de abreviação de URL.

Crie o arquivo IsGdTests.groovy correspondente, mostrado na Listagem 7, no diretório test/integration/org/grails/shortenurl. Digite grails test-app e verifique se a classe IsGd funciona conforme o esperado.

Listagem 7. Testando a classe IsGd
package org.grails.shortenurl

class IsGdTests extends GroovyTestCase{
  def transactional = false
  
  void testShorten(){
    def shortUrl = IsGd.shorten("http://grails.org")
    assertEquals "http://is.gd/2oCZR", shortUrl        
  }
  
  void testBadUrl(){
    def shortUrl = IsGd.shorten("IAmNotAValidUrl")
    println shortUrl
    assertTrue shortUrl.startsWith("An error occurred:")
  }
}

Para ver os detalhes de como exatamente o serviço IsGd falha quando você informa IAmNotAValidUrl, recomendo sair para uma linha de comando usando curl, conforme mostrado na Listagem 8. (O utilitário cURL é nativo em UNIX®/Linux®/Mac OS X, e pode ser baixado para o Windows®; consulte Recursos). Teste a URL incorreta no navegador para ver a mensagem de erro, e não o código de erro. Usando cURL, é possível ver claramente que o serviço da Web retorna o código 500 em lugar do código esperado 200.

Listagem 8. Usando curl para ver os detalhes da classe do serviço da Web com falha
$ curl --verbose "http://is.gd/api.php?longurl=IAmNotAValidUrl"
* About to connect() to is.gd port 80 (#0)
*   Trying 78.31.109.147... connected
* Connected to is.gd (78.31.109.147) port 80 (#0)
> GET /api.php?longurl=IAmNotAValidUrl HTTP/1.1
> User-Agent: curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3
                 OpenSSL/0.9.7l zlib/1.2.3
> Host: is.gd
> Accept: */*
> 
< HTTP/1.1 500 Internal Server Error
< X-Powered-By: PHP/5.2.6
< Content-type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Date: Wed, 19 Aug 2009 17:33:04 GMT
< Server: lighttpd/1.4.22
< 
* Connection #0 to host is.gd left intact
* Closing connection #0
Error: The URL entered was not valid.

Agora que a funcionalidade essencial do plug-in está implementada e testada, crie um serviço prático que expõe as duas classes do utilitário de uma maneira compatível com o Grails.

Criando o serviço ShortenUrl

Para criar um serviço, digite grails create-service ShortenUrl. Adicione o código da Listagem 9 ao grails-app/services/ShortenUrlService.groovy:

Listagem 9. O serviço ShortenUrl
import org.grails.shortenurl.*

class ShortenUrlService {
    boolean transactional = false

    def tinyurl(String longUrl) {
      return TinyUrl.shorten(longUrl)
    }

    def isgd(String longUrl) {
      def shortUrl = IsGd.shorten(longUrl)
      if(shortUrl.contains("error")){
        log.error(shortUrl)
      }
      return shortUrl
    }
}

Assim como com os testes de integração anteriores, defina o sinalizador transactional como false. Não há nenhum banco de dados envolvido nessas chamadas, então não é necessário agrupá-las em uma transação.

Observe que o método isgd() registra qualquer tentativa de abreviar uma URL inválida. Todos os artefatos do Grails recebem um objeto log no tempo de execução. É possível chamar métodos no objeto log que corresponde ao nível de log desejado: debug, info, error e assim por diante. (Consulte Recursos para obter mais informações sobre a criação de log). Como você verá em breve, trabalhar com este objeto log inserido ao escrever testes de unidade exige uma etapa adicional.

Quando o Grails criou o serviço para você, foi adicionado o teste correspondente ao diretório do teste/unidade. Geralmente, eu diria para você mover o ShortenUrlServiceTests.groovy para o diretório de teste/integração, pois ele é, semanticamente, um teste de integração e não um teste de unidade — o teste do serviço conta com recursos externos. Em vez disso, mantenha-o no diretório de teste/unidade para que eu possa mostrar algumas dicas de teste de unidade. Adicione o código da Listagem 10 ao ShortenUrlServiceTests.groovy:

Listagem 10. Testando o serviço ShortenUrl
import grails.test.*

class ShortenUrlServiceTests extends GrailsUnitTestCase {
    def transactional = false
    def shortenUrlService
  
    protected void setUp() {
        super.setUp()
        shortenUrlService = new ShortenUrlService()
    }

    protected void tearDown() {
        super.tearDown()
    }

    void testTinyUrl() {
      def shortUrl = shortenUrlService.tinyurl("http://grails.org")
      assertEquals "http://tinyurl.com/3xfpkv", shortUrl
    }

    void testIsGd() {
      def shortUrl = shortenUrlService.isgd("http://grails.org")
      assertEquals "http://is.gd/2oCZR", shortUrl        
    }

    void testIsGdWithBadUrl() {
      def shortUrl = shortenUrlService.isgd("IAmNotAValidUrl")
      assertTrue shortUrl.startsWith("An error occurred:")
    }
}

Observe, após definir o sinalizador transactional para false, você declara o shortenUrlService como variável. Em seguida, você inicializa o serviço no método setUp(). Os métodos setUp() e tearDown() são chamados para cada teste.

Se este fosse um teste de integração, seria executado sem erros. Mas como se trata de um teste de unidade, o método testIsGdWithBadUrl() falha com: No such property: log for class: ShortenUrlService. Abra test/reports/html/index.html no seu navegador da Web para ver a mensagem de erro mostrada na Figura 2:

Figura 2. Falha do teste de unidade resultante do objeto de log inserido
Falha do teste de unidade resultante do objeto de log inserido
Falha do teste de unidade resultante do objeto de log inserido

Como se vê, o objeto log não é inserido no serviço para o teste de unidade. (Lembre-se: Os testes de unidade devem ser executados em isolamento completo). Felizmente, é possível conseguir isso adicionando uma única linha — mockLogging(ShortenUrlService) — ao método setUp() como mostrado na Listagem 11:

Listagem 11. Simulando o objeto inserido log
protected void setUp() {
    super.setUp()
    mockLogging(ShortenUrlService)
    shortenUrlService = new ShortenUrlService()
}

O método mockLogging() insere um objeto log simulado no serviço. Esse criador de logs simulado envia sua saída para System.out em vez de enviar para qualquer um conectores log4j definidos. Para ver a saída (mostrada na Figura 3), digite grails test-app novamente e clique no link System.out link na parte inferior da página de relatórios HTML de ShortenUrlServiceTests.

Figura 3. Saída do criador de logs simulado
Saída do criador de logs simulado
Saída do criador de logs simulado

É possível empacotar diversos outros artefatos do Grails com este plug-in — um TagLib customizado para abreviar URLs em GSPs, um codec customizado — mas, com o que vimos até aqui, você já pode ter uma boa ideia do que um plug-in pode oferecer. Em seguida, você irá empacotar este plug-in como ele se encontra agora e integrá-lo a outro projeto do Grails.

Empacotando e implementando o plug-in

A fim de preparar todo o aplicativo Grails para implementação, normalmente você digitaria grails war. Para plug-ins, digite grails package-plugin em vez disso. Você deverá terminar com um arquivo grails-shortenurl-0.1.zip no seu diretório-raiz.

Lembre-se do artigo "Mastering Grails: Entendendo Plug-ins", que afirma que todos os plug-ins do Grails são distribuídos como arquivos ZIP. Se você olhar no diretório .grails/1.1.1/plugins em seu diretório inicial, deverá ver plug-ins com nomes semelhantes, como grails-hibernate-1.1.1.zip e grails-searchable-0.5.5.zip.

Se o ShortenUrl fosse um plug-in público, seria possível digitar grails release-plugin para executar um push das suas alterações ao portal de Plug-ins do Grails. Assim, qualquer pessoa poderia digitar grails install-plugin shortenurl para integrá-lo a um projeto. No entanto, é possível instalar plug-ins privados de forma igualmente fácil: tudo o que você tem de fazer é fornecer o caminho completo para o arquivo ZIP do seu sistema de arquivos local.

Para fazer esse teste, crie um diretório novo e vazio fora do diretório shortenurl. Digite grails create-app foo para criar uma aplicação simples. Mude para esse diretório e digite grails install-plugin /local/path/to/grails-shortenurl-0.1.zip, evidentemente substituindo o caminho real para o plug-in. Você deverá ver algo semelhante à saída mostrada na Listagem 12:

Listagem 12. Instalando um plug-in local
$ grails install-plugin /code/grails-shortenurl-0.1.zip
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /opt/grails

Base Directory: /code/foo
Running script /opt/grails/scripts/InstallPlugin.groovy
Environment set to development
     [copy] Copying 1 file to /Users/sdavis/.grails/1.1.1/plugins
     Installing plug-in shortenurl-0.1
     [mkdir] Created dir: 
     /Users/sdavis/.grails/1.1.1/projects/foo/plugins/shortenurl-0.1
     [unzip] Expanding: 
     /Users/sdavis/.grails/1.1.1/plugins/grails-shortenurl-0.1.zip into 
     /Users/sdavis/.grails/1.1.1/projects/foo/plugins/shortenurl-0.1
Executing shortenurl-0.1 plugin post-install script ...
Plugin shortenurl-0.1 installed

Como é possível ver, o ciclo de vida de plug-ins locais privados é idêntico ao dos plug-ins públicos.

Abra o arquivo foo/application.properties em um editor de texto. Verifique se plugins.shortenurl está listado, conforme mostrado na Listagem 13:

Listagem 13. Verificando se o plug-in aparece em application.properties
#utf-8
#Wed Aug 19 14:38:24 MDT 2009
app.version=0.1
app.servlet.version=2.4
app.grails.version=1.1.1
plugins.hibernate=1.1.1
plugins.shortenurl=0.1
app.name=foo

Agora que o plug-in está instalado, verifique se funciona conforme o esperado. Digite grails create-controller test. Abra grails-app/controllers/TestController.groovy e adicione o código da Listagem 14:

Listagem 14. Inserindo o serviço em um controlador
class TestController {
    def shortenUrlService

    def index = { 
      render "This is a test for the ShortenUrl plug-in 
" + "Type test/tinyurl?q=http://grails.org to try it out." } def tinyurl = { render shortenUrlService.tinyurl(params.q) } }

Observe que def shortenUrlService insere o serviço no controlador. Digite grails run-app para iniciar o aplicativo. Visite http://localhost:9090/foo/test/tinyurl?q=http://grails.org no seu navegador da Web. Você deverá ver resultados conforme exibidos na Figura 4:

Figura 4. Verificando a instalação bem-sucedida do plug-in
Verificando a instalação bem-sucedida do plug-in
Verificando a instalação bem-sucedida do plug-in

Além disso, se você visitar http://tinyurl.com/3xfpkv, com certeza chegará ao grails.org.

Conclusão

Como é possível ver, criar plug-ins Grails não é muito diferente de criar um típico aplicativo Grails. Em vez de digitar grails create-app, digite grails create-plugin. Em vez de digitar grails war, digite grails package-plugin. Além disso, excetuando-se a adição de importantes detalhes ao arquivo descritor GrailsPlugin.groovy, todas as etapas intermediárias (criação de serviços, escrita de testes e assim por diante) são idênticas.

Neste artigo, tratei brevemente dos recursos de simulação de testes de unidade do Grails com o método mockLogging(). Na próxima vez, demonstrarei outros diversos métodos de simulação incrivelmente úteis: mockDomain(), mockForConstraintsTests() e muito mais. Até lá, divirta-se dominando o Grails.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java, Software livre
ArticleID=431562
ArticleTitle=Mastering Grails: Criando um plug-in customizado
publish-date=09152009