Валидация XML файлов в Atlassian Stash

Date February 4th, 2014 Author Vitaly Agapov

Из тумана, как из форточки, выглянул Филин, ухнул: "Угу! У-гу-гу-гу!…" и растворился в тумане. "Псих", — подумал Ёжик, поднял сухую палку и, ощупывая ею туман, двинулся вперед.

Сергей Козлов «Ёжик в тумане»

good_codeЯ уже касался как-то темы написания хука для репозитория в Atlassian Stash. На этот раз появилась необходимость проверять валидность синтаксиса XML-файлов, которые коммитят в репозиторий. То есть задача состоит в том, чтобы в каждый момент времени обеспечить в репозитории набор валидных XML-файлов. Сама задача разбивается на две логические подзадачи: 1. получить содержимое изменённых файлов; 2. Произвести непосредственно проверку синтаксиса.

Более или менее рабочий пример можно посмотреть у меня в GitHub. А тут расскажу про решение подзадач.

Доступ к телу файла в Stash

Вообще для доступа к содержимому файлов в API Stash есть интерфейс ContentService, который в связке с HistoryService может вроде как сделать всё, что нам нужно. Но я к сожалению так и не смог его побороть и заставить делать то, что мне нужно. Сказывается куцая документация и отсутствие хороших примеров.

Но зато в том же API есть интерфейс GitScm для прямого вызова команд Git из плагина. Для его использования нужно добавить в pom.xml новую зависимость:

<dependency>
    <groupId>com.atlassian.stash</groupId>
    <artifactId>stash-scm-git-api</artifactId>        
    <scope>provided</scope>
    <version>${stash.version}</version>
</dependency>

Теперь в классе PreReceiveRepositoryHook можно будет импортировать:

import com.atlassian.stash.scm.git.GitCommandBuilderFactory;
import com.atlassian.stash.scm.git.GitScm;
import com.atlassian.stash.scm.git.GitScmCommandBuilder;

и создать объект private final GitScm gitScm и инициализировать его в конструкторе.

Теперь с его помощью можно, например, получить список всех файлов, которые изменились в текущих коммитах:

for (RefChange refChange : refChanges)
{
        String result = gitScm.getCommandBuilderFactory().builder(context.getRepository())
        .command("diff")
        .argument("--name-only")
        .argument(refChange.getFromHash())
        .argument(refChange.getToHash())
        .build(new StringCommandOutputHandler())
        .call();
}

После этого полученный список можно разбить на имена файлов, выбрать из них те, которые имеют расширение .xml, и получить содержимое файла в последнем коммите.

String[] files = result.split("\n");
for (String file : files) {
        if (file.matches(".+\\.xml$")) {
                String result = gitScm.getCommandBuilderFactory().builder(context.getRepository())
                .command("show")
                .argument(refChange.getToHash() + ":" + file)
                .build(new StringCommandOutputHandler())
                .call();
        }
}

Для работы ещё потребуется реализовать класс StringCommandOutputHandler. Самое простое – это реализация класса CommandOutputHandler и наследник StringOutputHandler:

package com.atlassian.stash.plugin;

import com.atlassian.stash.scm.CommandOutputHandler;
import com.atlassian.utils.process.StringOutputHandler;

public class StringCommandOutputHandler extends StringOutputHandler implements CommandOutputHandler<String> {
}

Правда, если запустить Stash и включить сейчас этот хук, то при его вызове свалится ошибка:

Unsatisfied dependency expressed through constructor argument with index 0 of type [com.atlassian.stash.scm.git.GitScm]: : No unique bean of type [com.atlassian.stash.scm.git.GitScm] is defined: Unsatisfied dependency of type [interface com.atlassian.stash.scm.git.GitScm]: expected at least 1 matching bean; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.atlassian.stash.scm.git.GitScm] is defined: Unsatisfied dependency of type [interface com.atlassian.stash.scm.git.GitScm]: expected at least 1 matching bean

Это из-за того, что GitScm не является частью ядра Stash и Maven-зависимости недостаточно. Нужно добавить этот интерфейс как компонент в atlassian-plugin.xml:

<component-import key="gitScm" interface="com.atlassian.stash.scm.git.GitScm" />

Половина дела сделана – текст файла есть у нас в String переменной. Следующий шаг:

Проверка синтаксиса XML

Проверять будем с помощью javax.xml.parsers.DocumentBuilder и javax.xml.parsers.DocumentBuilderFactory. В общем, выглядит решение так:

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import java.io.StringReader;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;

...
try {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setValidating(false);
         factory.setNamespaceAware(true);
         DocumentBuilder builder = factory.newDocumentBuilder();
         Document document = builder.parse(new InputSource(new StringReader(result)));
} catch (ParserConfigurationException e) {
         hookResponse.out().println(e);
         return false;
} catch (SAXException e) {
         hookResponse.out().println(file + " XML bad syntax: " + e);
         return false;
} catch (IOException e) {
         hookResponse.out().println(e);
         return false;
}
return true;

Tags: ,
Category: Java | No comments »

Comments

Leave a comment

 Comment Form