Статьи

Groovy как клиент API GoogleCode

В этой статье рассказывается о работе с API XML / HTTP из Groovy в контексте реального сценария, использующего API GoogleCode.

Эта статья впервые появилась в мартовском  выпуске GroovyMag за  2013 год . Поскольку сценарий был изначально написан, Google отказался от  API отслеживания ошибок  и запланировал его закрытие 14 июня 2013 года. Поэтому, хотя сценарий теперь предназначен только для интереса, принципы по-прежнему действительны для других целей.

Некоторое время назад у меня было требование переименовать проект Google Code — это было связано с опечаткой в ​​названии проекта, а не с уведомлением о прекращении действия. Как только мой коллега назначил мне разрешения на уровне владельца, я обнаружил, что даже расширенные возможности администрирования не позволяют вам изменять имя проекта. Однако, поскольку у Google есть проблемы, у них есть API, и, будучи непростым участником Groovy, было непросто перенести проблемы в новый проект (sans-typo).

Это было на раннем этапе жизненного цикла проекта, поэтому основной проблемой была не миграция кода (есть несколько постов в блоге с инструкциями по этому вопросу в зависимости от вашего вида контроля исходного кода) — скорее это было множество вопросов и комментариев от мастерская по сбору требований.

Окончательный скрипт доступен на GitHub в виде гистограммы: https://gist.github.com/rbramley/5073413

API и набор инструментов

API отслеживания проблем задокументирован по адресу http://code.google.com/p/support/wiki/IssueTrackerAPI.

Это RESTful API, использующий фиды / записи Atom, поэтому мы будем использовать следующие инструменты:

  • Apache HttpComponents HttpClient 4.x (заменено commons-httpclient 3.x)
  • XmlSlurper — для разбора Atom Feeds, которые мы получаем
  • MarkupBuilder — для создания записей Atom в POST

ЗАВИСИМОСТИ

HttpClient  и его зависимости можно получить с помощью @Grabаннотаций Groovy GRAPE,  показанных в листинге 1. GRAPE (GRoovy Advanced Packaging Engine — http://groovy.codehaus.org/Grape ) использует Apache Ivy для разрешения зависимостей и был представлен в Groovy 1.6.

@Grab(group='commons-logging', module='commons-logging', version='1.1.1')
@Grab(group='commons-codec', module='commons-codec', version='1.4')
@Grab(group='org.apache.httpcomponents', module='httpclient', version='4.1.2')

Листинг 1 : Схватить аннотации

Как работает скрипт?

Чтобы лучше понять контекст примеров, полезно быстро вспомнить, что делает сценарий — процесс выглядит следующим образом:

  1. Форма POST учетные данные
  2. Если не запрещено 403, извлеките токен авторизации (для использования в заголовке авторизации для всех последующих запросов)
  3. ПОЛУЧИТЬ список проблем из исходного проекта
  4. Разобрать тело ответа Atom XML (объявив пространство имен проблем http://schemas.google.com/projecthosting/issues/2009 )
  5. Для каждой записи в ленте:

    1. Извлеките идентификатор проблемы
    2. Преобразовать запись в требуемую форму
    3. POST запись Atom в URL создания проблемы целевого проекта
    4. Если не 400 плохих запросов, ПОЛУЧИТЕ список комментариев по идентификатору проблемы из исходного проекта
    5. Разобрать тело ответа Atom XML (объявляя пространство имен проблем как и прежде)
    6. Для каждой записи в ленте:

      1. Преобразовать запись в требуемую форму
      2. POST запись Atom в URL создания комментария целевого проекта для текущей проблемы

Основы HTTP-взаимодействия

Для работы с API требуется использование двух глаголов, GET и POST. Примеры опираются на определение HttpClient httpclient = new DefaultHttpClient()

ПОЛУЧИТЬ

get = new HttpGet(issuesCommentsListUrl)
get.setHeader('Authorization', "GoogleLogin auth=${authToken}")
response = httpclient.execute(get)

println "${response.getStatusLine().getStatusCode()} - ${response.getStatusLine().getReasonPhrase()}"
commentsAtom = EntityUtils.toString(response.entity)

Листинг 2 : Метод HttpClient GET

Комментарий к листингу 2 состоит из двух частей: создание запроса и обработка ответа. 
Для запроса мы должны  создать  объект HttpGet с целевым URL, добавить токен авторизации в заголовок и затем дать указание  HttpClient  выполнить наш запрос.
С точки зрения обработки ответа, мы требуем его как строку для синтаксического анализа XML, поэтому объект должен быть получен и преобразован в строку.

Хотя это довольно просто, он не превосходит простоту расширенного getText метода Groovy  в   классе java.net.URL (например  def response = new URL(url).getText()) — в этом случае  HttpClient  используется для согласованности и для возможности установки заголовков.

LOGIN FORM POST

Для первой операции POST требуется регистрация формы для входа в систему, и, как показано в листинге 3, параметры формы создаются с использованием NameValuePair и добавляются в список. Этот список используется для создания объекта формы в кодировке URL, установленного  для объекта HttpPost .

// set up login parameters
NameValuePair accountType = new BasicNameValuePair('accountType', 'GOOGLE')
NameValuePair email = new BasicNameValuePair('Email', emailAddress)
NameValuePair passwd = new BasicNameValuePair('Passwd', password)
NameValuePair service = new BasicNameValuePair('service', 'code')
NameValuePair source = new BasicNameValuePair('source', sourceScript)

List params = new ArrayList(5)
params.addAll()

HttpPost post = new HttpPost(googleLoginUrl)
post.setEntity(new UrlEncodedFormEntity(params))

HttpResponse response = httpclient.execute(post)

Листинг 3 : Форма POST

Если код состояния ответа не 403 Запрещено, полученный токен авторизации извлекается из тела ответа.

ПОСТИНГ XML

Как видно из листинга 4, операции POST с полезной нагрузкой XML проще, поскольку мы отправляем XML с использованием  StringEntity . В этом случае мы также должны установить Content-type заголовок  application/atom+xml.

// post the issue
post = new HttpPost(issuePostUrl)
post.setHeader('Content-type', 'application/atom+xml')
post.setHeader('Authorization', "GoogleLogin auth=${authToken}")
post.setEntity(new StringEntity(issueCreationXml))
response = httpclient.execute(post)

Листинг 4 : POST тела строки

Groovy обработка XML

В Groovy есть несколько очень полезных вспомогательных классов для работы с XML — в следующих разделах будут рассмотрены  XmlSlurper  и  MarkupBuilder,  используемые сценарием. GPath, мощный набор функций для запроса вложенных структур данных, таких как XML-документ, выходит за рамки этой статьи, но заслуживает дальнейшего изучения, если вам нужно выполнить более сложную обработку XML, чем требуется для упражнения по отображению в этом сценарии.

ПОТРЕБЛЕНИЕ С XMLSLURPER

Groovy XML Slurper ([a href = «http://groovy.codehaus.org/Reading+XML+using+Groovy%E2%80%99s+XmlSlurper» rel = «nofollow» style = «font-style: inherit; color: rgb (51, 51, 51); «] http://groovy.codehaus.org/Reading+XML+using+Groovy’s+XmlSlurper) может анализировать XML в дереве объектов и поддерживает пространство имен. В листинге 5 приведен обзор важных полей в ленте Atom, которые используются в листингах 6 и 7. Поскольку этот сценарий был разработан для переноса проблем, мы разбираем XML на вложенные объекты (листинг 6), которые используются для генерации XML для воссоздание проблемы в целевом проекте (листинг 7).

<feed xmlns='http://www.w3.org/2005/Atom' ...>
<entry>
 <id>
 <title>
 <content type='html'>
 <author>
 <name>
 </author>
 <issues:id>
 <issues:label>Type-Defect</issues:label>
 <issues:label>Priority-Medium</issues:label>
 <issues:owner>
 <issues:username>
 </issues:owner>
 <issues:state>
 <issues:status>
</entry>

Листинг 5 : Поля ввода Atom

// Parse and process the atom feed
feed = new XmlSlurper().parseText(atom).declareNamespace([issues:issuesXmlns])
feed.entry.each { entry ->
 issueId = entry.'issues:id'
 println "Issue ${issueId} - ${entry.title}"

 issueCreationXml = buildIssue(entry, issuesXmlns, atomXmlns)

Листинг 6 : XmlSlurper

ПРОИЗВОДСТВО С MARKUPBUILDER

MarkupBuilder  — это вспомогательный класс для создания разметки XML или HTML с использованием синтаксиса построителя на основе замыканий. В отличие от  XmlSlurper  groovy.xml.MarkupBuilder не входит в стандартный путь к классам Groovy, поэтому его необходимо импортировать. В листинге 7 показано создание записи Atom с расширениями API проблем данных Google.

Для детального контроля над конкретными элементами вы можете использовать  класс MarkupBuilderHelper ( http://groovy.codehaus.org/api/groovy/xml/MarkupBuilderHelper.html ), к которому осуществляется доступ  mkp и который позволяет напрямую вставлять экранированные или неэкранированные данные, например,  b { mkp.yield( '3 < 5' ) } какие результаты на выходе  3 <5 .

/** Build an atom feed for an issue */
def buildIssue(entry, issuesXmlns, atomXmlns) {
 def writer = new StringWriter()
 def xml = new MarkupBuilder(writer)
 
 xml.'atom:entry'('xmlns:atom':atomXmlns,'xmlns:issues':issuesXmlns) {
 'atom:title'(entry.title)
 'atom:content'(type:'html', entry.content)
 'atom:author' {
 'atom:name'(entry.author.name)
 }
 entry.'issues:label'.each {
 'issues:label'(it)
 }
 'issues:owner' {
 'issues:username'(entry.'issues:owner'.'issues:username')
 }
 'issues:state'(entry.'issues:state')
 'issues:status'(entry.'issues:status')
 }
 
 return writer.toString()
}

Листинг 7 : MarkupBuilder

GROOVY РУКОВОДСТВО ПОЛЬЗОВАТЕЛЯ

http://groovy.codehaus.org/GPath

http://groovy.codehaus.org/Processing+XML

http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlSlurper

http://groovy.codehaus.org/Creating+XML+using+Groovy%27s+MarkupBuilder

Executing the script

If you have the need to migrate your issues then the necessary steps are:

  1. Create your new target project
  2. Add the members from the old project (I did this manually) – this is critical to prevent creation errors
  3. Grab the IssueMigrator.groovy script from GitHub gist
  4. Change the project names, credentials and script source (lines 32-36)
  5. If you have more than 50 issues you’ll need to increase the max-results parameter on line 50
  6. Understand that the issues/comments will be created as the account running the script (and it has no warranty) – the original owners will be retained
  7. Run the script (either cross your fingers or run against a guinea pig project first!)
  8. Sit back and enjoy your new project

You may see some 400 ‘bad request’ responses being output from issue comments where it thinks there have been no updates. This can be ignored as the issue was created in the end state, rather than being created in the start state and then rolled forwards.